package com.codeanalysissys.backend.javascript;

import com.codeanalysissys.backend.graphs.ast.ASNode;
import com.codeanalysissys.backend.graphs.ast.AbstractSyntaxTree;
import com.codeanalysissys.backend.java.parser.JavaParser;
import com.codeanalysissys.backend.javascript.parser.JavaScriptBaseVisitor;
import com.codeanalysissys.backend.javascript.parser.JavaScriptLexer;
import com.codeanalysissys.backend.javascript.parser.JavaScriptParser;
import com.codeanalysissys.backend.utils.StringUtils;
import ghaffarian.graphs.Edge;
import ghaffarian.nanologger.Logger;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.misc.Interval;
import org.antlr.v4.runtime.tree.ParseTree;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;

public class JavaScriptASTBuilder {
    public static AbstractSyntaxTree build(String jsFile) throws IOException {
        return build(new File(jsFile));
    }

    public static AbstractSyntaxTree build(InputStream inFile) throws IOException {
        ANTLRInputStream input = new ANTLRInputStream(inFile);
        JavaScriptLexer lexer = new JavaScriptLexer(input);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        JavaScriptParser parser = new JavaScriptParser(tokens);
        ParseTree tree = parser.program();
        return build(tree, null, null);
    }

    /**
     * ‌Build and return the Abstract Syntax Tree (AST) for the given JavaScript source file.
     */
    public static AbstractSyntaxTree build(File jsFile) throws IOException {
        if (!jsFile.getName().endsWith(".js"))
            throw new IOException("Not a JavaScript File!");
        InputStream inFile = new FileInputStream(jsFile);
        ANTLRInputStream input = new ANTLRInputStream(inFile);
        JavaScriptLexer lexer = new JavaScriptLexer(input);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        JavaScriptParser parser = new JavaScriptParser(tokens);
        ParseTree tree = parser.program();
//        ParseTree tree=parser.sourceElements();
        return build(jsFile.getPath(), tree, null, null);
    }

    public static AbstractSyntaxTree build(String fileName, InputStream inputStream) throws IOException {

        InputStream inFile = inputStream;
        ANTLRInputStream input = new ANTLRInputStream(inFile);
        JavaScriptLexer lexer = new JavaScriptLexer(input);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        JavaScriptParser parser = new JavaScriptParser(tokens);
        ParseTree tree = parser.program();
        return build(fileName, tree, null, null);
    }

    public static AbstractSyntaxTree build(ParseTree tree, String propKey, Map<ParserRuleContext, Object> ctxProps) {
        JavaScriptASTBuilder.AbstractSyntaxVisitor visitor = new JavaScriptASTBuilder.AbstractSyntaxVisitor(propKey, ctxProps);
        return visitor.build(tree);
    }

    /**
     * ‌Build and return the Abstract Syntax Tree (AST) for the given Parse-Tree.
     * The 'ctxProps' map includes contextual-properties for particular nodes
     * in the parse-tree, which can be used for linking this graph with other
     * graphs by using the same parse-tree and the same contextual-properties.
     */
    public static AbstractSyntaxTree build(String filePath, ParseTree tree,
                                           String propKey, Map<ParserRuleContext, Object> ctxProps) {
        JavaScriptASTBuilder.AbstractSyntaxVisitor visitor = new JavaScriptASTBuilder.AbstractSyntaxVisitor(filePath, propKey, ctxProps);
        Logger.debug("Visitor building AST of: " + filePath);
        return visitor.build(tree);
    }

    private static class AbstractSyntaxVisitor extends JavaScriptBaseVisitor<String> {
        private String propKey;
        private String typeModifier;
        private String memberModifier;
        private Deque<ASNode> parentStack;
        private final AbstractSyntaxTree AST;
        private Map<String, String> vars, fields, methods, functions;
        private int varsCounter, fieldsCounter, methodsCounter, functionCounters;
        private Map<ParserRuleContext, Object> contexutalProperties;
        //解决break多次重复的问题
        private ASNode preBreak = null;

        public AbstractSyntaxVisitor(String filePath, String propKey, Map<ParserRuleContext, Object> ctxProps) {
            parentStack = new ArrayDeque<>();
            AST = new AbstractSyntaxTree(filePath);
            this.propKey = propKey;
            contexutalProperties = ctxProps;
            vars = new LinkedHashMap<>();
            fields = new LinkedHashMap<>();
            methods = new LinkedHashMap<>();
            functions = new LinkedHashMap<>();
            varsCounter = 0;
            fieldsCounter = 0;
            methodsCounter = 0;
            functionCounters = 0;
        }

        public AbstractSyntaxVisitor(String propKey, Map<ParserRuleContext, Object> ctxProps) {
            parentStack = new ArrayDeque<>();
            AST = new AbstractSyntaxTree();
            this.propKey = propKey;
            contexutalProperties = ctxProps;
            vars = new LinkedHashMap<>();
            fields = new LinkedHashMap<>();
            methods = new LinkedHashMap<>();
            functions = new LinkedHashMap<>();
            varsCounter = 0;
            fieldsCounter = 0;
            methodsCounter = 0;
            functionCounters = 0;
        }

        public AbstractSyntaxTree build(ParseTree tree) {
            // 创建AST根节点并设置内容
            JavaScriptParser.ProgramContext rootCntx = (JavaScriptParser.ProgramContext) tree;
            AST.root.setCode(AST.filePath != null ? new File(AST.filePath).getName() : "root");

            // 根节点压入栈
            parentStack.push(AST.root);
            if (rootCntx.sourceElements() != null) {
                visit(rootCntx.sourceElements());
            }
            parentStack.pop();
            vars.clear();
            fields.clear();
            methods.clear();
            return AST;
        }

        //TODO 待解决，会进到这里吗？
        @Override
        public String visitProgram(JavaScriptParser.ProgramContext ctx) {
//            // packageDeclaration :  annotation* 'package' qualifiedName ';'
//            ASNode node = new ASNode(ASNode.Type.PACKAGE);
//            node.setCode(ctx.qualifiedName().getText());
//            node.setLineOfCode(ctx.getStart().getLine());
//            Logger.debug("Adding package");
//            AST.addVertex(node);
//            AST.addEdge(parentStack.peek(), node);
            if (ctx == null) {
                return "";
            }
            ASNode node = new ASNode(ASNode.Type.SOURCEELEMENTS);
            node.setCode(ctx.sourceElements().getText());
            node.setLineOfCode(ctx.getStart().getLine());
            Logger.debug("Adding sourceElements");
            AST.addVertex(node);
            AST.addEdge(parentStack.peek(), node);
            parentStack.add(node);
            StringBuffer stringBuffer = new StringBuffer();
            if (ctx.HashBangLine() != null) {
                stringBuffer.append(ctx.HashBangLine().getText() + " ");
            }
            if (ctx.sourceElements() != null && !ctx.sourceElements().isEmpty()) {
                stringBuffer.append(visitSourceElements(ctx.sourceElements()) + " ");
            }
            stringBuffer.append(ctx.EOF().getText());
            parentStack.pop();
            return stringBuffer.toString();
        }

        @Override
        public String visitSourceElements(JavaScriptParser.SourceElementsContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            if (ctx.sourceElement() != null && ctx.sourceElement().size() > 0) {
                for (JavaScriptParser.SourceElementContext sourceContext : ctx.sourceElement()) {
                    // visit每个sourceContext，他们是什么就会进到哪个visit中
//                    visitSourceElement(sourceContext);
                    visit(sourceContext);
                }
            }
            return "";
        }


        @Override
        public String visitSourceElement(JavaScriptParser.SourceElementContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            return visit(ctx.statement());
        }

//        @Override
//        public String visitStatement(JavaScriptParser.StatementContext ctx) {
//            ctx.
//            return "";
//        }


//        @Override
//        public String visitBlock(JavaScriptParser.BlockContext ctx) {
//            if (ctx == null) {
//                return "";
//            }
//            ASNode node = new ASNode(ASNode.Type.BLOCK);
//            node.setLineOfCode(ctx.getStart().getLine());
//            AST.addVertex(node);
//            AST.addEdge(parentStack.peek(), node);
//            parentStack.add(node);
//            for (JavaScriptParser.StatementContext statementContext : ctx.statementList().statement()) {
//                visit(statementContext);
//            }
//            parentStack.pop();
////            return res;
//            return null;
//        }

//        @Override
//        public String visitStatementList(JavaScriptParser.StatementListContext ctx) {
//            if (ctx == null) {
//                return "";
//            }
//            ASNode node = new ASNode(ASNode.Type.STATEMENTLIST);
//            node.setLineOfCode(ctx.getStart().getLine());
//            AST.addVertex(node);
//            AST.addEdge(parentStack.peek(), node);
//            parentStack.add(node);
//            for (JavaScriptParser.StatementContext statementContext : ctx.statement()) {
//                visit(statementContext);
//            }
//            parentStack.pop();
//            return null;
//        }

        @Override
        public String visitImportStatement(JavaScriptParser.ImportStatementContext ctx) {
            if (ctx == null) {
                return "";
            }
            //importStatement
            //    : Import importFromBlock
            //    ;
            //importFromBlock
            //    : importDefault? (importNamespace | moduleItems) importFrom eos
            //    | StringLiteral eos
            //    ;
            //moduleItems
            //    : '{' (aliasName ',')* (aliasName ','?)? '}'
            //    ;
            //importDefault
            //    : aliasName ','
            //    ;
            //importNamespace
            //  : ('*' | identifierName) (As identifierName)?
            //   ;
            //importFrom
            //    : From StringLiteral
            //    ;
            // 创建import语句根节点
            ASNode node = new ASNode(ASNode.Type.IMPORT);
            // 设置代码行号及内容
            node.setLineOfCode(ctx.getStart().getLine());
            // todo java中直接整行作为一个节点了没拆，js中是否有必要拆下？若拆下则根节点的node要改
            node.setCode(getOriginalCodeText(ctx.importFromBlock()));
            Logger.debug("Adding import " + getOriginalCodeText(ctx.importFromBlock()));
            // 加入AST
            AST.addVertex(node);
            AST.addEdge(parentStack.peek(), node);
//            parentStack.push(node);
//            String res = visit(ctx.Import()) + " " + visit(ctx.importFromBlock());
//            parentStack.pop();

            // todo 是否要加imports?
            // 若加，则要判断parent.peek，若不是就加上
            // 但要在每个statement前判断是否是imports，是的话要弹出来，但其实应该只用在class和function声明中加？

            // todo java中直接整行作为一个节点了没拆，js中是否有必要拆下？若拆下就需要以下部分
            // 压入栈
//            parentStack.push(node);
//            visit(ctx.importFromBlock());
//            parentStack.pop();
            return "";
        }

        @Override
        public String visitImportFromBlock(JavaScriptParser.ImportFromBlockContext ctx) {
            if (ctx == null) {
                return "";
            }
            //importDefault? (importNamespace | moduleItems) importFrom eos
            //    | StringLiteral eos
            ASNode node = new ASNode(ASNode.Type.IMPORTFROMBLOCK);
            node.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(node);
            AST.addEdge(parentStack.peek(), node);
            parentStack.push(node);
            StringBuffer res = new StringBuffer();
            if (ctx.importFrom() != null && !ctx.importFrom().isEmpty()) {
                if (ctx.importDefault() != null && !ctx.importDefault().isEmpty()) {
                    res.append(visitImportDefault(ctx.importDefault()));
                }
                if (ctx.importNamespace() != null && !ctx.importNamespace().isEmpty()) {
                    res.append(" " + visitImportNamespace(ctx.importNamespace()));
                }
                if (ctx.moduleItems() != null && !ctx.moduleItems().isEmpty()) {
                    res.append(" " + visitModuleItems(ctx.moduleItems()));
                }
                res.append(" " + visitImportFrom(ctx.importFrom()));
                res.append(" " + visitEos(ctx.eos()));
            } else {
                res.append(ctx.StringLiteral().getText() + " " + visitEos(ctx.eos()));
            }
            parentStack.pop();
            return res.toString();
        }

        @Override
        public String visitModuleItems(JavaScriptParser.ModuleItemsContext ctx) {
            if (ctx == null) {
                return "";
            }
            StringBuffer stringBuffer = new StringBuffer();
            stringBuffer.append("{");
            ASNode node = new ASNode(ASNode.Type.MODULEITEMS);
            node.setLineOfCode(ctx.getStart().getLine());
            parentStack.add(node);
            stringBuffer.append(visitAliasName(ctx.aliasName(0)));
            for (int i = 1; i < ctx.aliasName().size(); i++) {
                stringBuffer.append("," + visitAliasName(ctx.aliasName(i)));
            }
            parentStack.pop();
            stringBuffer.append("}");
            return stringBuffer.toString();
        }

        @Override
        public String visitImportDefault(JavaScriptParser.ImportDefaultContext ctx) {
            if (ctx == null) {
                return "";
            }
            return visit(ctx.aliasName()) + " ,";
        }

        @Override
        public String visitImportNamespace(JavaScriptParser.ImportNamespaceContext ctx) {
            if (ctx == null) {
                return "";
            }
            //importNamespaceDeclration:     : ('*' | identifierName) (As identifierName)?
            ASNode node = new ASNode(ASNode.Type.IMPORTNAMESPACE);
            node.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(node);
            AST.addEdge(parentStack.peek(), node);
            parentStack.push(node);
            StringBuffer stringBuffer = new StringBuffer();
            if (ctx.identifierName() != null) {
                stringBuffer.append(visit(ctx.identifierName(0)));
            } else {
                stringBuffer.append("* ");
            }
            if (ctx.identifierName().size() > 1) {
                stringBuffer.append(visit(ctx.As()));
                stringBuffer.append(" ");
                stringBuffer.append(visit(ctx.identifierName(1)));
            }
            parentStack.peek();
            return stringBuffer.toString();
        }

        @Override
        public String visitImportFrom(JavaScriptParser.ImportFromContext ctx) {
            if (ctx == null) {
                return "";
            }
            ASNode node = new ASNode(ASNode.Type.IMPORTFROM);
            node.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(node);
            AST.addEdge(parentStack.peek(), node);
            return ctx.From().getText() + " " + ctx.StringLiteral().getText();
        }

        @Override
        public String visitAliasName(JavaScriptParser.AliasNameContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            ASNode node = new ASNode(ASNode.Type.ALIASNAME);
            node.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(node);
            AST.addEdge(parentStack.peek(), node);
            parentStack.add(node);
            String res = visitIdentifierName(ctx.identifierName(0)) + " " + ctx.As().getText() + " " + visitIdentifierName(ctx.identifierName(1));
            parentStack.pop();
            return res;
        }

        @Override
        public String visitExportDeclaration(JavaScriptParser.ExportDeclarationContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            ASNode exportNode = new ASNode(ASNode.Type.EXPORT);
            exportNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(exportNode);
            AST.addEdge(parentStack.peek(), exportNode);
            parentStack.add(exportNode);
            String res = "";
            if (ctx.exportFromBlock() != null && !ctx.exportFromBlock().isEmpty()) {
                visit(ctx.exportFromBlock());
            } else {
                visit(ctx.declaration());
            }
            parentStack.pop();
            return "";
        }

        // todo 搞下export
        @Override
        public String visitExportDefaultDeclaration(JavaScriptParser.ExportDefaultDeclarationContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            ASNode exportNode = new ASNode(ASNode.Type.EXPORT);
            exportNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(exportNode);
            AST.addEdge(parentStack.peek(), exportNode);
            return ctx.Export().getText() + " " + ctx.Default().getText() + " " + visit(ctx.singleExpression()) + " " + visitEos(ctx.eos());
        }

        @Override
        public String visitExportFromBlock(JavaScriptParser.ExportFromBlockContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            ASNode node = new ASNode(ASNode.Type.EXPORTFROMBLOCK);
            node.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(node);
            AST.addEdge(parentStack.peek(), node);
            parentStack.push(node);
            String res = null;
            if (ctx.importNamespace() != null && !ctx.importNamespace().isEmpty()) {
                res = visitImportNamespace(ctx.importNamespace()) + " " + visitImportFrom(ctx.importFrom()) + " " + visitEos(ctx.eos());
            } else {
                res = visitModuleItems(ctx.moduleItems()) + " " + visitImportFrom(ctx.importFrom()) + " " + visitEos(ctx.eos());
            }
            parentStack.pop();
            return res;
        }

//        @Override
//        public String visitDeclaration(JavaScriptParser.DeclarationContext ctx) {
//            if (ctx == null || ctx.isEmpty()) {
//                return "";
//            }
//            if (ctx.variableStatement() != null && !ctx.variableStatement().isEmpty()) {
//                return visitVariableStatement((ctx.variableStatement()));
//            } else if (ctx.classDeclaration() != null && !ctx.classDeclaration().isEmpty()) {
//                return visitClassDeclaration(ctx.classDeclaration());
//            }
//            return visitFunctionDeclaration(ctx.functionDeclaration());
//        }

        @Override
        public String visitVariableStatement(JavaScriptParser.VariableStatementContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
//            ASNode expressionNode = new ASNode(ASNode.Type.STATEMENT);
//            expressionNode.setLineOfCode(ctx.getStart().getLine());
//            AST.addVertex(expressionNode);
//            AST.addEdge(parentStack.peek(), expressionNode);
            if (ctx.variableDeclarationList() != null && !ctx.variableDeclarationList().isEmpty()) {
                return visitVariableDeclarationList(ctx.variableDeclarationList());
            }
            return null;
        }

        /**
         * 处理变量声明列表
         *
         * @param ctx
         * @return
         */
        @Override
        public String visitVariableDeclarationList(JavaScriptParser.VariableDeclarationListContext ctx) {
            //variableDeclarationList
            //    : varModifier variableDeclaration (',' variableDeclaration)*
            //    ;
            //
            //variableDeclaration
            //    : assignable ('=' singleExpression)? // ECMAScript 6: Array & Object Matching
            //    ;
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }

            for (JavaScriptParser.VariableDeclarationContext varCtx : ctx.variableDeclaration()) {
                // 创建变量声明根节点
                ASNode varNode = new ASNode(ASNode.Type.VARIABLE);
                varNode.setLineOfCode(varCtx.getStart().getLine());
                AST.addVertex(varNode);
                AST.addEdge(parentStack.peek(), varNode);

                // 创建修饰符节点
                ASNode modifierNode = new ASNode(ASNode.Type.MODIFIER);
                modifierNode.setCode(getOriginalCodeText(ctx.varModifier()));
                modifierNode.setLineOfCode(ctx.varModifier().getStart().getLine());
                AST.addVertex(modifierNode);
                AST.addEdge(varNode, modifierNode);

                // 创建变量名节点
                ASNode nameNode = new ASNode(ASNode.Type.NAME);
                String normalized = "$VARL_" + ++varsCounter;
                vars.put(varCtx.assignable().getText(), normalized);
                nameNode.setCode(varCtx.assignable().getText());
                nameNode.setNormalizedCode(normalized);
                nameNode.setLineOfCode(varCtx.assignable().getStart().getLine());
                AST.addVertex(nameNode);
                AST.addEdge(varNode, nameNode);

                // 若存在初始化
                if (varCtx.singleExpression() != null) {
                    ASNode initNode = new ASNode(ASNode.Type.INIT_VALUE);
                    initNode.setCode("init");
                    initNode.setLineOfCode(varCtx.singleExpression().getStart().getLine());
                    AST.addVertex(initNode);
                    AST.addEdge(varNode, initNode);
                    parentStack.push(initNode);
                    visit(varCtx.singleExpression());
                    parentStack.pop();
                }
            }

            return "";
        }

//        @Override
//        public String visitVariableDeclaration(JavaScriptParser.VariableDeclarationContext ctx) {
//            if (ctx == null || ctx.isEmpty()) {
//                return "";
//            }
//            return visitAssignable(ctx.assignable()) + "=" + visit(ctx.singleExpression());
//        }

        @Override
        public String visitEmptyStatement_(JavaScriptParser.EmptyStatement_Context ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            ASNode emptyNode = new ASNode(ASNode.Type.EMPTY);
            emptyNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(emptyNode);
            AST.addEdge(parentStack.peek(), emptyNode);
            return ctx.SemiColon().getText();
        }

//        @Override
//        public String visitExpressionStatement(JavaScriptParser.ExpressionStatementContext ctx) {
//            if (ctx == null || ctx.isEmpty()) {
//                return "";
//            }
//            //    : {this.notOpenBraceAndNotFunction()}? expressionSequence eos
//            ASNode expressionNode = new ASNode(ASNode.Type.STATEMENT);
//            expressionNode.setLineOfCode(ctx.getStart().getLine());
//            AST.addVertex(expressionNode);
//            AST.addEdge(parentStack.peek(), expressionNode);
//            if (ctx.expressionSequence() != null && !ctx.expressionSequence().isEmpty()) {
//                return visitExpressionSequence(ctx.expressionSequence());
//            }
//            return visitEos(ctx.eos());
//        }

        @Override
        public String visitIfStatement(JavaScriptParser.IfStatementContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            //     : If '(' expressionSequence ')' statement (Else statement)?
            // 创建if根节点
            ASNode ifNode = new ASNode(ASNode.Type.IF);
            ifNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(ifNode);
            AST.addEdge(parentStack.peek(), ifNode);

            // if condition
            ASNode cond = new ASNode(ASNode.Type.CONDITION);
//            cond.setCode(getOriginalCodeText(ctx.expressionSequence()));
//            cond.setNormalizedCode(visit(ctx.expressionSequence()));
            cond.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(cond);
            AST.addEdge(ifNode, cond);
            parentStack.push(cond);
            visit(ctx.expressionSequence());
            parentStack.pop();

            // if then
            ASNode thenNode = new ASNode(ASNode.Type.THEN);
            thenNode.setLineOfCode(ctx.statement(0).getStart().getLine());
            AST.addVertex(thenNode);
            AST.addEdge(ifNode, thenNode);
            parentStack.push(thenNode);
            visit(ctx.statement(0));
            parentStack.pop();

            // if else
            if (ctx.statement(1) != null) {
                ASNode elseNode = new ASNode(ASNode.Type.ELSE);
                elseNode.setLineOfCode(ctx.statement(1).getStart().getLine());
                AST.addVertex(elseNode);
                AST.addEdge(ifNode, elseNode);
                parentStack.push(elseNode);
                visit(ctx.statement(1));
                parentStack.pop();
            }
            return "";
        }

        @Override
        public String visitDoStatement(JavaScriptParser.DoStatementContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            //     : Do statement While '(' expressionSequence ')' eos                                                                       # DoStatement
            // do 根节点
            ASNode doWhileNode = new ASNode(ASNode.Type.DO_WHILE);
            doWhileNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(doWhileNode);
            AST.addEdge(parentStack.peek(), doWhileNode);

            // do block
            ASNode block = new ASNode(ASNode.Type.BLOCK);
            block.setLineOfCode(ctx.statement().getStart().getLine());
            AST.addVertex(block);
            AST.addEdge(doWhileNode, block);
            parentStack.push(block);
            visit(ctx.statement());
            parentStack.pop();

            // do cond
            ASNode cond = new ASNode(ASNode.Type.CONDITION);
//            cond.setCode(getOriginalCodeText(ctx.expressionSequence()));
//            cond.setNormalizedCode(visit(ctx.expressionSequence()));
            cond.setLineOfCode(ctx.expressionSequence().getStart().getLine());
            AST.addVertex(cond);
            AST.addEdge(doWhileNode, cond);
            parentStack.push(cond);
            visit(ctx.expressionSequence());
            parentStack.pop();
            return "";
        }

        @Override
        public String visitWhileStatement(JavaScriptParser.WhileStatementContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            //     | While '(' expressionSequence ')' statement                                                                              # WhileStatement
            // while root
            ASNode whileNode = new ASNode(ASNode.Type.WHILE);
            whileNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(whileNode);
            AST.addEdge(parentStack.peek(), whileNode);

            // while cond
            ASNode cond = new ASNode(ASNode.Type.CONDITION);
            cond.setCode(getOriginalCodeText(ctx.expressionSequence()));
            cond.setNormalizedCode(visit(ctx.expressionSequence()));
            cond.setLineOfCode(ctx.expressionSequence().getStart().getLine());
            AST.addVertex(cond);
            AST.addEdge(whileNode, cond);
            parentStack.push(cond);
            visit(ctx.expressionSequence());
            parentStack.pop();

            // while block
            ASNode block = new ASNode(ASNode.Type.BLOCK);
            block.setLineOfCode(ctx.statement().getStart().getLine());
            AST.addVertex(block);
            AST.addEdge(whileNode, block);
            parentStack.push(block);
            visit(ctx.statement());
            parentStack.pop();
            return "";
        }

        @Override
        public String visitForStatement(JavaScriptParser.ForStatementContext ctx) {
            //  ForStatement    | For '(' (expressionSequence | variableDeclarationList)? ';' expressionSequence? ';' expressionSequence? ')' statement   # ForStatement
            //
            //  expressionSequence: singleExpression (',' singleExpression)*
            //
            //  variableDeclarationList: varModifier variableDeclaration (',' variableDeclaration)*
            //
            //  varModifier: Var| let_ | Const
            //
            //  variableDeclaration: assignable ('=' singleExpression)?
            //
            //  assignable: identifier| arrayLiteral| objectLiteral
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }

            // for root
            ASNode forNode;
            forNode = new ASNode(ASNode.Type.FOR);
            forNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(forNode);
            AST.addEdge(parentStack.peek(), forNode);

            // 定位
            int index = 0;

            // for init
            ASNode forInit = new ASNode(ASNode.Type.FOR_INIT);
            AST.addVertex(forInit);
            AST.addEdge(forNode, forInit);

            if (ctx.expressionSequence().size() == 3) {
                // 三个都有
                forInit.setLineOfCode(ctx.expressionSequence(index).getStart().getLine());
                parentStack.push(forInit);
                visit(ctx.expressionSequence(index));
                parentStack.pop();

                // 后移一位
                index++;
            } else {
                // 不是三个都有，可能是variableDeclarationList
                if (ctx.variableDeclarationList() != null) {
                    forInit.setLineOfCode(ctx.variableDeclarationList().getStart().getLine());
                    parentStack.push(forInit);
                    visit(ctx.variableDeclarationList());
                    parentStack.pop();
                }
            }

            // for cond
            if (ctx.expressionSequence(index) != null) {
                ASNode condNode = new ASNode(ASNode.Type.CONDITION);
                condNode.setLineOfCode(ctx.expressionSequence(index).getStart().getLine());
                AST.addVertex(condNode);
                AST.addEdge(forNode, condNode);
                parentStack.push(condNode);
                visit(ctx.expressionSequence(index));
                parentStack.pop();
                index++;
            }

            // for update
            if (ctx.expressionSequence(index) != null) {
                ASNode updateNode = new ASNode(ASNode.Type.FOR_UPDATE);
                updateNode.setLineOfCode(ctx.expressionSequence(index).getStart().getLine());
                AST.addVertex(updateNode);
                AST.addEdge(forNode, updateNode);
                parentStack.push(updateNode);
                visit(ctx.expressionSequence(index));
                parentStack.pop();
            }

            // for block
            if (ctx.statement() != null) {
                ASNode block = new ASNode(ASNode.Type.BLOCK);
                block.setLineOfCode(ctx.statement().getStart().getLine());
                AST.addVertex(block);
                AST.addEdge(forNode, block);
                parentStack.push(block);
                visit(ctx.statement());
                parentStack.pop();
            }

            return "";
        }

        @Override
        public String visitForInStatement(JavaScriptParser.ForInStatementContext ctx) {
            //    | For '(' (singleExpression | variableDeclarationList) In expressionSequence ')' statement                                # ForInStatement
            //
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            // for in root
            ASNode forNode = new ASNode(ASNode.Type.FOR_EACH);
            forNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(forNode);
            AST.addEdge(parentStack.peek(), forNode);

            // for in var
            if (ctx.singleExpression() != null) {
                ASNode expr = new ASNode(ASNode.Type.EXPRESSION);
                expr.setCode(getOriginalCodeText(ctx.singleExpression()));
                AST.addVertex(expr);
                AST.addEdge(forNode, expr);
                parentStack.push(expr);
                visit(ctx.singleExpression());
                parentStack.pop();
            } else {
                ASNode varDel = new ASNode(ASNode.Type.VARIABLE);
                varDel.setLineOfCode(ctx.variableDeclarationList().getStart().getLine());
                AST.addVertex(varDel);
                AST.addEdge(forNode, varDel);
                parentStack.push(varDel);
                visit(ctx.variableDeclarationList());
                parentStack.pop();
            }

            // for in
            ASNode inNode = new ASNode(ASNode.Type.IN);
            AST.addVertex(inNode);
            AST.addEdge(forNode, inNode);

            // for in exprSeq
            ASNode exprSeq = new ASNode(ASNode.Type.EXPRESSION);
            exprSeq.setLineOfCode(ctx.expressionSequence().getStart().getLine());
            AST.addVertex(exprSeq);
            AST.addEdge(forNode, exprSeq);
            parentStack.push(exprSeq);
            visit(ctx.expressionSequence());
            parentStack.pop();

            // for in block
            ASNode block = new ASNode(ASNode.Type.BLOCK);
            block.setLineOfCode(ctx.statement().getStart().getLine());
            AST.addVertex(block);
            AST.addEdge(forNode, block);
            parentStack.push(block);
            visit(ctx.statement());
            parentStack.pop();
            return "";
        }

        @Override
        public String visitForOfStatement(JavaScriptParser.ForOfStatementContext ctx) {
            //    | For Await? '(' (singleExpression | variableDeclarationList) identifier{this.p("of")}? expressionSequence ')' statement  # ForOfStatement
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            // for of root
            ASNode forNode = new ASNode(ASNode.Type.FOR_EACH);
            forNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(forNode);
            AST.addEdge(parentStack.peek(), forNode);

            // for of var
            if (ctx.singleExpression() != null) {
                ASNode expr = new ASNode(ASNode.Type.EXPRESSION);
                expr.setCode(getOriginalCodeText(ctx.singleExpression()));
                AST.addVertex(expr);
                AST.addEdge(forNode, expr);
                parentStack.push(expr);
                visit(ctx.singleExpression());
                parentStack.pop();
            } else {
                ASNode varDel = new ASNode(ASNode.Type.VARIABLE);
                varDel.setLineOfCode(ctx.variableDeclarationList().getStart().getLine());
                AST.addVertex(varDel);
                AST.addEdge(forNode, varDel);
                parentStack.push(varDel);
                visit(ctx.variableDeclarationList());
                parentStack.pop();
            }

            // for of
            ASNode ofNode = new ASNode(ASNode.Type.OF);
            AST.addVertex(ofNode);
            AST.addEdge(forNode, ofNode);

            // for of exprSeq
            ASNode exprSeq = new ASNode(ASNode.Type.EXPRESSION);
            exprSeq.setLineOfCode(ctx.expressionSequence().getStart().getLine());
            AST.addVertex(exprSeq);
            AST.addEdge(forNode, exprSeq);
            parentStack.push(exprSeq);
            visit(ctx.expressionSequence());
            parentStack.pop();

            // for of block
            ASNode block = new ASNode(ASNode.Type.BLOCK);
            block.setLineOfCode(ctx.statement().getStart().getLine());
            AST.addVertex(block);
            AST.addEdge(forNode, block);
            parentStack.push(block);
            visit(ctx.statement());
            parentStack.pop();
            return "";
        }

//        @Override
//        public String visitVarModifier(JavaScriptParser.VarModifierContext ctx) {
//            if (ctx == null || ctx.isEmpty()) {
//                return "";
//            }
//            if (ctx.Var() != null) {
//                return ctx.Var().getText();
//            } else if (ctx.let_() != null && !ctx.let_().isEmpty()) {
//                return visitLet_(ctx.let_());
//            }
//            return ctx.Const().getText();
//        }

        @Override
        public String visitContinueStatement(JavaScriptParser.ContinueStatementContext ctx) {
            // TODO   : Continue ({this.notLineTerminator()}? identifier)? eos
            ASNode continueNode = new ASNode(ASNode.Type.CONTINUE);
            continueNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(continueNode);
            AST.addEdge(parentStack.peek(), continueNode);

            if (ctx.identifier() != null) {
                ASNode labelNode = new ASNode(ASNode.Type.LABELED);
                continueNode.setNormalizedCode("continue $LABEL");
                labelNode.setLineOfCode(ctx.identifier().getStart().getLine());
                labelNode.setCode(ctx.identifier().getText());
                AST.addVertex(labelNode);
                AST.addEdge(continueNode, labelNode);
            }
            return "";
        }

        @Override
        public String visitBreakStatement(JavaScriptParser.BreakStatementContext ctx) {
            //    : Break ({this.notLineTerminator()}? identifier)? eos
            ASNode breakNode = new ASNode(ASNode.Type.BREAK);
            breakNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(breakNode);
            AST.addEdge(parentStack.peek(), breakNode);

            if (ctx.identifier() != null) {
                ASNode labelNode = new ASNode(ASNode.Type.LABELED);
                breakNode.setNormalizedCode("break $LABEL");
                labelNode.setLineOfCode(ctx.identifier().getStart().getLine());
                labelNode.setCode(ctx.identifier().getText());
                AST.addVertex(labelNode);
                AST.addEdge(breakNode, labelNode);
            }
            return "";
        }

        @Override
        public String visitReturnStatement(JavaScriptParser.ReturnStatementContext ctx) {
            //:        : Return ({this.notLineTerminator()}? expressionSequence)? eos
            ASNode returnNode = new ASNode(ASNode.Type.RETURN);
            returnNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(returnNode);
            AST.addEdge(parentStack.peek(), returnNode);

            if (ctx.expressionSequence() != null) {
                parentStack.push(returnNode);
                visit(ctx.expressionSequence());
                parentStack.pop();
            }
            return "";
        }

        @Override
        public String visitYieldStatement(JavaScriptParser.YieldStatementContext ctx) {
            //:         : Yield ({this.notLineTerminator()}? expressionSequence)? eos
            ASNode yieldNode = new ASNode(ASNode.Type.YIELD);
            yieldNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(yieldNode);
            AST.addEdge(parentStack.peek(), yieldNode);

            if (ctx.expressionSequence() != null) {
                parentStack.push(yieldNode);
                visit(ctx.expressionSequence());
                parentStack.pop();
            }
            return "";
        }

        @Override
        public String visitWithStatement(JavaScriptParser.WithStatementContext ctx) {
            //:      : With '(' expressionSequence ')' statement
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            // with root
            ASNode withNode = new ASNode(ASNode.Type.WITH);
//            withNode.setCode(getOriginalCodeText(ctx.expressionSequence()));
//            withNode.setNormalizedCode(visit(ctx.expressionSequence()));
            withNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(withNode);
            AST.addEdge(parentStack.peek(), withNode);

            // with expr
            parentStack.push(withNode);
            visit(ctx.expressionSequence());
            parentStack.pop();

            // with stmt
            ASNode stmt = new ASNode(ASNode.Type.STATEMENT);
            stmt.setLineOfCode(ctx.statement().getStart().getLine());
            AST.addVertex(stmt);
            AST.addEdge(withNode, stmt);
            parentStack.push(stmt);
            visit(ctx.statement());
            parentStack.pop();

            return "";
        }

        @Override
        public String visitSwitchStatement(JavaScriptParser.SwitchStatementContext ctx) {
            //    : Switch '(' expressionSequence ')' caseBlock
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            // switch root
            ASNode switchNode = new ASNode(ASNode.Type.SWITCH);
            switchNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(switchNode);
            AST.addEdge(parentStack.peek(), switchNode);
            parentStack.push(switchNode);

            // expr
            ASNode exprNode = new ASNode(ASNode.Type.EXPRESSION);
//            exprNode.setCode(getOriginalCodeText(ctx.expressionSequence()));
//            exprNode.setNormalizedCode(visit(ctx.expressionSequence().singleExpression(0)));
            exprNode.setLineOfCode(ctx.expressionSequence().getStart().getLine());
            AST.addVertex(exprNode);
            AST.addEdge(switchNode, exprNode);
            parentStack.push(exprNode);
            visit(ctx.expressionSequence());
            parentStack.pop();

            //    : '{' caseClauses? (defaultClause caseClauses?)? '}'
            if (ctx.caseBlock().caseClauses() != null) {
                //caseClauses
                //    : caseClause+
                //      : Case expressionSequence ':' statementList?
                for (JavaScriptParser.CaseClausesContext caseClauses : ctx.caseBlock().caseClauses()) {
                    for (JavaScriptParser.CaseClauseContext caseClause : caseClauses.caseClause()) {
                        ASNode caseNode = new ASNode(ASNode.Type.CASEBLOCK);
                        caseNode.setLineOfCode(caseClause.getStart().getLine());
                        AST.addVertex(caseNode);
                        AST.addEdge(parentStack.peek(), caseNode);
                        parentStack.push(caseNode);

                        ASNode subExpr = new ASNode(ASNode.Type.EXPRESSION);
                        subExpr.setLineOfCode(caseClause.expressionSequence().getStart().getLine());
                        AST.addVertex(subExpr);
                        AST.addEdge(parentStack.peek(), subExpr);
                        parentStack.push(subExpr);
                        for (JavaScriptParser.SingleExpressionContext singleExpressionContext : caseClause.expressionSequence().singleExpression()) {
                            if (singleExpressionContext.getChildCount() == 1) {
                                ASNode tokenNode;
                                tokenNode = vars.get(getOriginalCodeText(singleExpressionContext)) != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                                tokenNode.setLineOfCode(singleExpressionContext.getStart().getLine());
                                tokenNode.setCode(getOriginalCodeText(singleExpressionContext));
                                AST.addVertex(tokenNode);
                                AST.addEdge(parentStack.peek(), tokenNode);
                            } else {
                                visit(singleExpressionContext);
                            }
                        }
                        parentStack.pop();

                        ASNode stmt = new ASNode(ASNode.Type.STATEMENT);
                        stmt.setLineOfCode(caseClause.statementList().getStart().getLine());
                        AST.addVertex(stmt);
                        AST.addEdge(parentStack.peek(), stmt);
                        parentStack.push(stmt);
                        visit(caseClause.statementList());
                        parentStack.pop();

                        parentStack.pop();
                    }
                }


//                for (JavaScriptParser.CaseClausesContext caseClausesContext : ctx.caseBlock().caseClauses()) {
//                    ASNode caseBlockNode = new ASNode(ASNode.Type.CASEBLOCK);
////                    caseBlockNode.setCode("");
//                    caseBlockNode.setLineOfCode(ctx.caseBlock().getStart().getLine());
//                    AST.addVertex(caseBlockNode);
//                    AST.addEdge(switchNode, caseBlockNode);
//                    if (caseClausesContext.caseClause() != null) {
//                        for (JavaScriptParser.CaseClauseContext clauseContext : caseClausesContext.caseClause()) {
//                            // : Case expressionSequence ':' statementList?
//                            ASNode caseNode = new ASNode(ASNode.Type.CASE);
//                            caseNode.setLineOfCode(ctx.getStart().getLine());
//                            caseNode.setCode(ctx.getText());
//                            AST.addVertex(caseNode);
//                            AST.addEdge(caseBlockNode, caseNode);
//                            ASNode exprNode = new ASNode(ASNode.Type.STATEMENT);
//                            exprNode.setCode(ctx.expressionSequence().getText());
//                            exprNode.setLineOfCode(ctx.expressionSequence().getStart().getLine());
//                            exprNode.setNormalizedCode(visit(ctx.expressionSequence()));
//                            AST.addVertex(exprNode);
//                            AST.addEdge(caseNode, exprNode);
//                            visitStatementList(exprNode, clauseContext.statementList());
//                        }
//                    }
//                }
//                ASNode caseBlockNode=new ASNode(ASNode.Type.CASEBLOCK);
//                caseBlockNode.setCode(getOriginalCodeText(ctx.caseBlock()));
//                caseBlockNode.setNormalizedCode(visit(ctx.caseBlock()));
//                caseBlockNode.setLineOfCode(ctx.caseBlock().getStart().getLine());
//                AST.addVertex(caseBlockNode);
//                AST.addEdge(switchNode, caseBlockNode);
            }
            if (ctx.caseBlock().defaultClause() != null) {
                // defaultClause
                //    : Default ':' statementList?
                //    ;
                JavaScriptParser.DefaultClauseContext defaultClauseCtx = ctx.caseBlock().defaultClause();
                if (defaultClauseCtx.Default() != null) {
                    ASNode caseNode = new ASNode(ASNode.Type.CASEBLOCK);
                    caseNode.setLineOfCode(defaultClauseCtx.getStart().getLine());
                    AST.addVertex(caseNode);
                    AST.addEdge(parentStack.peek(), caseNode);
                    parentStack.push(caseNode);

                    ASNode defaultNode = new ASNode(ASNode.Type.DEFAULT);
                    AST.addVertex(defaultNode);
                    AST.addEdge(parentStack.peek(), defaultNode);

                    ASNode stmt = new ASNode(ASNode.Type.STATEMENT);
                    AST.addVertex(stmt);
                    AST.addEdge(parentStack.peek(), stmt);
                    parentStack.push(stmt);
                    visit(defaultClauseCtx.statementList());
                    parentStack.pop();

                    parentStack.pop();
//                    ASNode caseNode = new ASNode(ASNode.Type.CASE);
//                    caseNode.setLineOfCode(ctx.getStart().getLine());
//                    caseNode.setCode(ctx.getText());
//                    AST.addVertex(caseNode);
//                    AST.addEdge(switchNode, caseNode);
                }
            }
            parentStack.pop();
            return "";
        }

        public String visitStatementList(ASNode exprNode, JavaScriptParser.StatementListContext ctx) {
            if (ctx == null) {
                return "";
            }
            for (JavaScriptParser.StatementContext statementContext : ctx.statement()) {
                visit(statementContext);
            }
            return "";
        }

        @Override
        public String visitCaseBlock(JavaScriptParser.CaseBlockContext ctx) {
            //    : '{' caseClauses? (defaultClause caseClauses?)? '}'
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            ASNode caseBlockNode = new ASNode(ASNode.Type.CASEBLOCK);
            caseBlockNode.setLineOfCode(ctx.getStart().getLine());
            caseBlockNode.setCode(ctx.getText());
            AST.addVertex(caseBlockNode);
            if (ctx.caseClauses() == null) {
                parentStack.push(caseBlockNode);
                visitDefaultClause(ctx.defaultClause());
                parentStack.pop();
            } else {
                parentStack.push(caseBlockNode);
                for (JavaScriptParser.CaseClausesContext caseClause : ctx.caseClauses()) {
                    visitCaseClauses(caseClause);
                }
                parentStack.pop();
            }
            return "";
        }


        @Override
        public String visitCaseClauses(JavaScriptParser.CaseClausesContext ctx) {
            //caseClauses
            //    : caseClause+
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            ASNode caseClausesNode = new ASNode(ASNode.Type.CASECLAUSES);
            caseClausesNode.setLineOfCode(ctx.getStart().getLine());
            caseClausesNode.setCode(ctx.getText());
            AST.addVertex(caseClausesNode);
            AST.addEdge(parentStack.peek(), caseClausesNode);
            parentStack.push(caseClausesNode);
            for (JavaScriptParser.CaseClauseContext caseContext : ctx.caseClause()) {
                visitCaseClause(caseContext);
            }
            parentStack.pop();
            return "";
        }

        private void visitCaseClause(ASNode caseClausesNode, List<JavaScriptParser.CaseClauseContext> caseClauses) {
            for (JavaScriptParser.CaseClauseContext caseClause : caseClauses) {
                ASNode caseNode = new ASNode(ASNode.Type.CASE);
                caseNode.setLineOfCode(caseClause.getStart().getLine());
                caseNode.setCode(caseClause.getText());
                AST.addVertex(caseNode);
                AST.addEdge(caseClausesNode, caseNode);
            }
        }

        @Override
        public String visitCaseClause(JavaScriptParser.CaseClauseContext ctx) {
            // : Case expressionSequence ':' statementList?
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            ASNode caseNode = new ASNode(ASNode.Type.CASE);
            caseNode.setLineOfCode(ctx.getStart().getLine());
            caseNode.setCode(ctx.getText());
            AST.addVertex(caseNode);
            AST.addEdge(parentStack.peek(), caseNode);
            ASNode exprNode = new ASNode(ASNode.Type.STATEMENT);
            exprNode.setCode(ctx.expressionSequence().getText());
            exprNode.setLineOfCode(ctx.expressionSequence().getStart().getLine());
            exprNode.setNormalizedCode(visit(ctx.expressionSequence()));
            AST.addVertex(exprNode);
            AST.addEdge(caseNode, exprNode);
            ASNode stateNode = new ASNode(ASNode.Type.STATEMENT);
            stateNode.setCode(ctx.statementList().getText());
            stateNode.setLineOfCode(ctx.statementList().getStart().getLine());
            stateNode.setNormalizedCode(visit(ctx.statementList()));
            AST.addVertex(stateNode);
            AST.addEdge(caseNode, stateNode);
            return "";
        }

        @Override
        public String visitDefaultClause(JavaScriptParser.DefaultClauseContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            if (ctx.Default() != null) {
                ASNode caseNode = new ASNode(ASNode.Type.CASE);
                caseNode.setLineOfCode(ctx.getStart().getLine());
                caseNode.setCode(ctx.getText());
                AST.addVertex(caseNode);
                AST.addEdge(parentStack.peek(), caseNode);
                return "Default : " + visit(ctx.statementList());
            }
            return "";
        }


        @Override
        public String visitLabelledStatement(JavaScriptParser.LabelledStatementContext ctx) {
            //    : identifier ':' statement
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            ASNode labelNode = new ASNode(ASNode.Type.LABELED);
            labelNode.setLineOfCode(ctx.identifier().getStart().getLine());
            labelNode.setCode(ctx.identifier().getText());
            AST.addVertex(labelNode);
            AST.addEdge(parentStack.peek(), labelNode);
            parentStack.push(labelNode);
            visit(ctx.statement());
            parentStack.pop();

            return "";
        }

        @Override
        public String visitThrowStatement(JavaScriptParser.ThrowStatementContext ctx) {
            //:      : Throw {this.notLineTerminator()}? expressionSequence eos
            ASNode throwNode = new ASNode(ASNode.Type.THROW);
            throwNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(throwNode);
            AST.addEdge(parentStack.peek(), throwNode);
            parentStack.push(throwNode);
            visit(ctx.expressionSequence());
            parentStack.pop();
            return "";
        }

        @Override
        public String visitTryStatement(JavaScriptParser.TryStatementContext ctx) {
            //  : Try block (catchProduction finallyProduction? | finallyProduction)
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            ASNode tryNode = new ASNode(ASNode.Type.TRY);
            tryNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(tryNode);
            AST.addEdge(parentStack.peek(), tryNode);
            //
            ASNode tryBlock = new ASNode(ASNode.Type.BLOCK);
            tryBlock.setLineOfCode(ctx.block().getStart().getLine());
            AST.addVertex(tryBlock);
            AST.addEdge(tryNode, tryBlock);
            parentStack.push(tryBlock);
            visit(ctx.block());
            parentStack.pop();

            // catchProduction : : Catch ('(' assignable? ')')? block
            if (ctx.catchProduction() != null) {
                ASNode catchNode = new ASNode(ASNode.Type.CATCH);
                AST.addVertex(catchNode);
                AST.addEdge(tryNode, catchNode);
                ++varsCounter;
                ASNode catchName = new ASNode(ASNode.Type.NAME);
                String normalized = "$VARL_" + varsCounter;
                vars.put(ctx.catchProduction().getText(), normalized);
                catchName.setCode(ctx.catchProduction().getText());
                catchName.setNormalizedCode(normalized);
                catchName.setLineOfCode(ctx.catchProduction().getStart().getLine());
                AST.addVertex(catchName);
                AST.addEdge(catchNode, catchName);
                //
                ASNode catchBlock = new ASNode(ASNode.Type.BLOCK);
                catchBlock.setLineOfCode(ctx.catchProduction().block().getStart().getLine());
                AST.addVertex(catchBlock);
                AST.addEdge(catchNode, catchBlock);
                parentStack.push(catchBlock);
                visit(ctx.catchProduction().block());
                parentStack.pop();
            }
            // finallyBlock :  'finally' block
            if (ctx.finallyProduction() != null) {
                ASNode finallyNode = new ASNode(ASNode.Type.FINALLY);
                finallyNode.setLineOfCode(ctx.finallyProduction().getStart().getLine());
                AST.addVertex(finallyNode);
                AST.addEdge(tryNode, finallyNode);
                parentStack.push(finallyNode);
                visit(ctx.finallyProduction().block());
                parentStack.pop();
            }
            return "";
        }

        @Override
        public String visitCatchProduction(JavaScriptParser.CatchProductionContext ctx) {
            //: Catch ('(' assignable? ')')? block
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            ASNode catchNode = new ASNode(ASNode.Type.CATCH);
            catchNode.setLineOfCode(ctx.block().getStart().getLine());
            AST.addVertex(catchNode);
            if (ctx.assignable() != null) {
                ASNode expr = new ASNode(ASNode.Type.STATEMENT);
                expr.setLineOfCode(ctx.assignable().getStart().getLine());
                AST.addVertex(expr);
                AST.addEdge(catchNode, expr);
            }
            ASNode blockNode = new ASNode(ASNode.Type.BLOCK);
            blockNode.setLineOfCode(ctx.block().getStart().getLine());
            AST.addVertex(blockNode);
            AST.addEdge(catchNode, blockNode);
            parentStack.push(catchNode);
            return visitChildren(ctx);
        }

        @Override
        public String visitFinallyProduction(JavaScriptParser.FinallyProductionContext ctx) {
            //: : Finally block
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            ASNode finalNode = new ASNode(ASNode.Type.FINALLY);
            finalNode.setLineOfCode(ctx.block().getStart().getLine());
            AST.addVertex(finalNode);
            ASNode blockNode = new ASNode(ASNode.Type.BLOCK);
            blockNode.setLineOfCode(ctx.block().getStart().getLine());
            AST.addVertex(blockNode);
            AST.addEdge(finalNode, blockNode);
            parentStack.push(finalNode);
            return visitChildren(ctx);
        }

        @Override
        public String visitDebuggerStatement(JavaScriptParser.DebuggerStatementContext ctx) {
            //    : Debugger eos
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            ASNode debuggerNode = new ASNode(ASNode.Type.DEBUGGER);
            debuggerNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(debuggerNode);
            AST.addEdge(parentStack.peek(), debuggerNode);
            return "";
        }

        @Override
        public String visitFunctionDeclaration(JavaScriptParser.FunctionDeclarationContext ctx) {
            //      : Async? Function_ '*'? identifier '(' formalParameterList? ')' functionBody
            //TODO
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            // 创建方法根节点并与爹建边
            ASNode functionNode = new ASNode(ASNode.Type.FUNCTION);
            functionNode.setLineOfCode(ctx.getStart().getLine());
            functionNode.setNormalizedCode("$LABEL function");
//            functionNode.setCode(getOriginalCodeText(ctx));
            AST.addVertex(functionNode);
            AST.addEdge(parentStack.peek(), functionNode);
            // 作为方法内部节点们的爹
            parentStack.push(functionNode);

            StringBuilder modifiers = new StringBuilder();
            if (ctx.Async() != null) {
                modifiers.append("Async ");
            }
            if (ctx.Function_() != null) {
                for (int i = 0; i < ctx.getChildCount(); i++) {
                    if ("*".equals(ctx.getChild(i).getText()))
                        modifiers.append("*");
                }
            }

            if (!modifiers.isEmpty()) {

                ASNode modifierNode = new ASNode(ASNode.Type.MODIFIER);
                modifierNode.setCode(modifiers.toString());
                modifierNode.setLineOfCode(ctx.getStart().getLine());
                Logger.debug("Adding method modifier");
                AST.addVertex(modifierNode);
                AST.addEdge(parentStack.peek(), modifierNode);
            }


            //javaScript有返回值，但不需要像java一样显示声明出来
//            ASNode retNode = new ASNode(ASNode.Type.RETURN);
//            retNode.setCode(ctx.getChild(0).getText());
//            retNode.setLineOfCode(ctx.getStart().getLine());
//            Logger.debug("Adding method type");
//            AST.addVertex(retNode);
//            AST.addEdge(parentStack.peek(), retNode);
//            //
//            ++methodsCounter;

            // 处理函数名
            ASNode nameNode = new ASNode(ASNode.Type.NAME);
            String functionName = ctx.identifier().Identifier().getText();
            String normalized = "$FUNCTION_" + ++functionCounters;
            functions.put(functionName, normalized);
            nameNode.setCode(functionName);
            nameNode.setNormalizedCode(normalized);
            nameNode.setLineOfCode(ctx.getStart().getLine());
            Logger.debug("Adding method name");
            // 加入ast与爹建边
            AST.addVertex(nameNode);
            AST.addEdge(parentStack.peek(), nameNode);

            // 处理形参列表
            if (ctx.formalParameterList() != null) {
                // 创建参数根节点
                ASNode paramsNode = new ASNode(ASNode.Type.PARAMS);
                paramsNode.setLineOfCode(ctx.formalParameterList().getStart().getLine());
                Logger.debug("Adding method params node");
                AST.addVertex(paramsNode);
                AST.addEdge(parentStack.peek(), paramsNode);

                // 压入栈处理儿子们
                parentStack.push(paramsNode);
                for (JavaScriptParser.FormalParameterArgContext paramctx : ctx.formalParameterList().formalParameterArg()) {
                    ASNode varNode = new ASNode(ASNode.Type.VARIABLE);
                    varNode.setLineOfCode(paramctx.getStart().getLine());
                    AST.addVertex(varNode);
                    AST.addEdge(parentStack.peek(), varNode);
                    ASNode name = new ASNode(ASNode.Type.NAME);
                    normalized = "$VARL_" + ++varsCounter;
                    vars.put(paramctx.assignable().getText(), normalized);
                    name.setCode(paramctx.assignable().getText());
                    name.setNormalizedCode(normalized);
                    name.setLineOfCode(paramctx.assignable().getStart().getLine());
                    AST.addVertex(name);
                    AST.addEdge(varNode, name);

                    // 处理默认值
                    if (paramctx.singleExpression() != null) {
                        // 存在默认值
                        ASNode defaultNode = new ASNode(ASNode.Type.DEFAULT);
                        defaultNode.setLineOfCode(paramctx.singleExpression().getStart().getLine());
                        AST.addVertex(defaultNode);
                        AST.addEdge(varNode, defaultNode);
                        parentStack.push(defaultNode);
                        // 不管是变量/常量/表达式，让他自己处理然后与爹建边
                        visit(paramctx.singleExpression());
                        parentStack.pop();
                    }
                }
                if (ctx.formalParameterList().lastFormalParameterArg() != null) {
                    ASNode varNode = new ASNode(ASNode.Type.VARIABLE);
                    varNode.setLineOfCode(ctx.formalParameterList().lastFormalParameterArg().getStart().getLine());
                    AST.addVertex(varNode);
                    AST.addEdge(parentStack.peek(), varNode);
                    //
                    ++varsCounter;
                    ASNode name = new ASNode(ASNode.Type.NAME);
                    normalized = "$VARL_" + varsCounter;
                    vars.put(ctx.formalParameterList().lastFormalParameterArg().getText(), normalized);
                    name.setCode(ctx.formalParameterList().lastFormalParameterArg().getText());
                    name.setNormalizedCode(normalized);
                    name.setLineOfCode(ctx.formalParameterList().lastFormalParameterArg().getStart().getLine());
                    AST.addVertex(name);
                    AST.addEdge(varNode, name);

                    // 处理默认值
                    if (ctx.formalParameterList().lastFormalParameterArg().singleExpression() != null) {
                        // 存在默认值
                        ASNode defaultNode = new ASNode(ASNode.Type.DEFAULT);
                        defaultNode.setLineOfCode(ctx.formalParameterList().lastFormalParameterArg().singleExpression().getStart().getLine());
                        AST.addVertex(defaultNode);
                        AST.addEdge(varNode, defaultNode);
                        parentStack.push(defaultNode);
                        // 不管是变量/常量/表达式，让他自己处理然后与爹建边
                        visit(ctx.formalParameterList().lastFormalParameterArg().singleExpression());
                        parentStack.pop();
                    }
                }
                parentStack.pop();
            }
            //
            if (ctx.functionBody() != null) {
                // 创建函数体根节点
                ASNode functionBody = new ASNode(ASNode.Type.BLOCK);
                functionBody.setLineOfCode(ctx.functionBody().getStart().getLine());
                Logger.debug("Adding function block");
                AST.addVertex(functionBody);
                AST.addEdge(parentStack.peek(), functionBody);
                parentStack.push(functionBody);
                // todo which one?
                visitChildren(ctx.functionBody());
//                visitChildren(ctx.functionBody().sourceElements());
                parentStack.pop();
                resetLocalVars();
            }

            parentStack.pop();
            return "";
        }


        private void resetLocalVars() {
            vars.clear();
            varsCounter = 0;
        }

        @Override
        public String visitClassDeclaration(JavaScriptParser.ClassDeclarationContext ctx) {
            //    : Class identifier classTail
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            // 创建类声明的根节点
            ASNode classNode = new ASNode(ASNode.Type.CLASS);
            classNode.setLineOfCode(ctx.getStart().getLine());
            Logger.debug("Adding class node");
            AST.addVertex(classNode);
            AST.addEdge(parentStack.peek(), classNode);

            // js中的类没有修饰符
//            ASNode modifierNode = new ASNode(ASNode.Type.MODIFIER);
//            modifierNode.setCode(typeModifier);
//            modifierNode.setLineOfCode(ctx.getStart().getLine());
//            Logger.debug("Adding class modifier");
//            AST.addVertex(modifierNode);
//            AST.addEdge(classNode, modifierNode);

            // 创建类名
            ASNode nameNode = new ASNode(ASNode.Type.NAME);
            String className = ctx.identifier().Identifier().getText();
            nameNode.setCode(className);
            nameNode.setLineOfCode(ctx.getStart().getLine());
            Logger.debug("Adding class name: " + className);
            // 与class根节点建边
            AST.addVertex(nameNode);
            AST.addEdge(classNode, nameNode);

            // 访问类内部
            parentStack.push(classNode);
            visit(ctx.classTail());
            parentStack.pop();
            return "";
        }

        @Override
        public String visitClassTail(JavaScriptParser.ClassTailContext ctx) {
            //: (Extends singleExpression)? '{' classElement* '}'
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            if (ctx.Extends() != null) {
                ASNode extendsNode = new ASNode(ASNode.Type.EXTENDS);
                extendsNode.setCode(getOriginalCodeText(ctx.singleExpression()));
                extendsNode.setLineOfCode(ctx.singleExpression().getStart().getLine());
                Logger.debug("Adding extends " + ctx.singleExpression().getText());
                AST.addVertex(extendsNode);
                AST.addEdge(parentStack.peek(), extendsNode);
            }

            // 访问每个类成员
            for (JavaScriptParser.ClassElementContext classElementContext : ctx.classElement()) {
                visit(classElementContext);
            }

            return "";
        }

        // todo 是否需要存在？
//        @Override
//        public String visitClassElement(JavaScriptParser.ClassElementContext ctx) {
//            //    : (Static | {this.n("static")}? identifier | Async)* (methodDefinition | assignable '=' objectLiteral ';')
//            //    | emptyStatement_
//            //    | '#'? propertyName '=' singleExpression
//            //TODO
//            if (ctx == null || ctx.isEmpty()) {
//                return "";
//            }
//            if (ctx.emptyStatement_() != null && !ctx.emptyStatement_().isEmpty()) {
//                return visitEmptyStatement_(ctx.emptyStatement_());
//            } else if (ctx.propertyName() != null && !ctx.propertyName().isEmpty()) {
//                return "#" + visitPropertyName(ctx.propertyName()) + "=" + visit(ctx.singleExpression());
//            }
//            StringBuffer stringBuffer = new StringBuffer();
//            for (int i = 0; i < ctx.Static().size(); i++) {
//                stringBuffer.append(ctx.Static(i).getText() + " ");
//                stringBuffer.append(visitIdentifier(ctx.identifier(i)) + " ");
//                stringBuffer.append(ctx.Async(i).getText() + " ");
//            }
//            if(ctx.methodDefinition()!=null&&!ctx.methodDefinition().isEmpty()){
//                stringBuffer.append(visitMethodDefinition(ctx.methodDefinition()));
//            }
//            if(ctx.assignable()!=null&&!ctx.assignable().isEmpty()){
//                stringBuffer.append(visitAssignable(ctx.assignable())+" ");
//            }
//            stringBuffer.append(visitObjectLiteral(ctx.objectLiteral())+";");
//            return stringBuffer.toString();
//        }

        @Override
        public String visitMethodDefinition(JavaScriptParser.MethodDefinitionContext ctx) {
            //TODO
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            // 创建方法根节点并与爹建边
            ASNode methodNode = new ASNode(ASNode.Type.METHOD);
            methodNode.setLineOfCode(ctx.getStart().getLine());
            methodNode.setNormalizedCode("$LABEL method");
//            methodNode.setCode(getOriginalCodeText(ctx));
            AST.addVertex(methodNode);
            AST.addEdge(parentStack.peek(), methodNode);
            // 作为方法内部节点们的爹
            parentStack.add(methodNode);

            // 处理方法名前的修饰符
            StringBuilder modifiers = new StringBuilder();
            for (int i = 0; i < ctx.getChildCount(); i++) {
                String childText = ctx.getChild(i).getText();
                if ("*".equals(childText)) {
                    modifiers.append("* ");
                } else if ("#".equals(childText)) {
                    modifiers.append("#");
                    break;
                }
            }
            if (!StringUtils.isEmpty(modifiers.toString())) {
                // 若修饰符不为空，则创建节点
                ASNode modifierNode = new ASNode(ASNode.Type.MODIFIER);
                modifierNode.setCode(modifiers.toString());
                modifierNode.setLineOfCode(ctx.getStart().getLine());
                AST.addVertex(modifierNode);
                AST.addEdge(parentStack.peek(), modifierNode);
            }

            // 处理方法名
            ASNode nameNode;
            String methodName;
            if (ctx.propertyName() != null) {
                // normal
                nameNode = new ASNode(ASNode.Type.NAME);
                methodName = ctx.propertyName().getText();
            } else if (ctx.getter() != null) {
                // getter
                nameNode = new ASNode(ASNode.Type.GETTER);
                methodName = ctx.getter().getText();
            } else {
                // setter
                nameNode = new ASNode(ASNode.Type.SETTER);
                methodName = ctx.setter().getText();
            }
            String normalized = "$METHOD_" + ++methodsCounter;
            methods.put(methodName, normalized);
            nameNode.setCode(methodName);
            nameNode.setNormalizedCode(normalized);
            nameNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(nameNode);
            AST.addEdge(parentStack.peek(), nameNode);

            // 处理参数列表
            if (ctx.formalParameterList() != null) {
                // 创建参数根节点
                ASNode paramsNode = new ASNode(ASNode.Type.PARAMS);
                paramsNode.setLineOfCode(ctx.formalParameterList().getStart().getLine());
                Logger.debug("Adding method params node");
                AST.addVertex(paramsNode);
                AST.addEdge(parentStack.peek(), paramsNode);

                // 压入栈处理儿子们
                parentStack.push(paramsNode);
                for (JavaScriptParser.FormalParameterArgContext paramctx : ctx.formalParameterList().formalParameterArg()) {
                    ASNode varNode = new ASNode(ASNode.Type.VARIABLE);
                    varNode.setLineOfCode(paramctx.getStart().getLine());
                    AST.addVertex(varNode);
                    AST.addEdge(parentStack.peek(), varNode);
                    ASNode name = new ASNode(ASNode.Type.NAME);
                    normalized = "$VARL_" + ++varsCounter;
                    vars.put(paramctx.assignable().getText(), normalized);
                    name.setCode(paramctx.assignable().getText());
                    name.setNormalizedCode(normalized);
                    name.setLineOfCode(paramctx.assignable().getStart().getLine());
                    AST.addVertex(name);
                    AST.addEdge(varNode, name);

                    // 处理默认值
                    if (paramctx.singleExpression() != null) {
                        // 存在默认值
                        ASNode defaultNode = new ASNode(ASNode.Type.DEFAULT);
                        defaultNode.setLineOfCode(paramctx.singleExpression().getStart().getLine());
                        AST.addVertex(defaultNode);
                        AST.addEdge(varNode, defaultNode);
                        parentStack.push(defaultNode);
                        // 不管是变量/常量/表达式，让他自己处理然后与爹建边
                        visit(paramctx.singleExpression());
                        parentStack.pop();
                    }
                }
                if (ctx.formalParameterList().lastFormalParameterArg() != null) {
                    ASNode varNode = new ASNode(ASNode.Type.VARIABLE);
                    varNode.setLineOfCode(ctx.formalParameterList().lastFormalParameterArg().getStart().getLine());
                    AST.addVertex(varNode);
                    AST.addEdge(parentStack.peek(), varNode);
                    //
                    ++varsCounter;
                    ASNode name = new ASNode(ASNode.Type.NAME);
                    normalized = "$VARL_" + varsCounter;
                    vars.put(ctx.formalParameterList().lastFormalParameterArg().getText(), normalized);
                    name.setCode(ctx.formalParameterList().lastFormalParameterArg().getText());
                    name.setNormalizedCode(normalized);
                    name.setLineOfCode(ctx.formalParameterList().lastFormalParameterArg().getStart().getLine());
                    AST.addVertex(name);
                    AST.addEdge(varNode, name);

                    // 处理默认值
                    if (ctx.formalParameterList().lastFormalParameterArg().singleExpression() != null) {
                        // 存在默认值
                        ASNode defaultNode = new ASNode(ASNode.Type.DEFAULT);
                        defaultNode.setLineOfCode(ctx.formalParameterList().lastFormalParameterArg().singleExpression().getStart().getLine());
                        AST.addVertex(defaultNode);
                        AST.addEdge(varNode, defaultNode);
                        parentStack.push(defaultNode);
                        // 不管是变量/常量/表达式，让他自己处理然后与爹建边
                        visit(ctx.formalParameterList().lastFormalParameterArg().singleExpression());
                        parentStack.pop();
                    }
                }
                parentStack.pop();
            }

            // 处理方法体
            if (ctx.functionBody() != null) {
                ASNode methodBody = new ASNode(ASNode.Type.BLOCK);
                methodBody.setLineOfCode(ctx.functionBody().getStart().getLine());
                Logger.debug("Adding method block");
                AST.addVertex(methodBody);
                AST.addEdge(parentStack.peek(), methodBody);
                parentStack.push(methodBody);
                visitChildren(ctx.functionBody().sourceElements());
                parentStack.pop();
                resetLocalVars();
            }

            parentStack.pop();
            return "";
        }

//        @Override
//        public String visitFormalParameterList(JavaScriptParser.FormalParameterListContext ctx) {
//            ASNode parameterListNode = new ASNode(ASNode.Type.PARAMETERLIST);
//            parameterListNode.setLineOfCode(ctx.getStart().getLine());
//            parameterListNode.setCode(getOriginalCodeText(ctx));
//            AST.addVertex(parameterListNode);
//            AST.addEdge(parentStack.peek(), parameterListNode);
//            parentStack.add(parameterListNode);
//            if (ctx.formalParameterArg() != null && ctx.formalParameterArg().size() > 0) {
//                for (JavaScriptParser.FormalParameterArgContext argCtx : ctx.formalParameterArg()) {
//                    ASNode argNode = new ASNode(ASNode.Type.PARAMETERARG);
//                    argNode.setLineOfCode(argCtx.getStart().getLine());
//                    argNode.setCode(getOriginalCodeText(argCtx));
//                    AST.addVertex(argNode);
//                    AST.addEdge(parentStack.peek(), argNode);
//                }
//            }
//            if (ctx.lastFormalParameterArg() != null) {
//                ASNode argNode = new ASNode(ASNode.Type.PARAMETERARG);
//                argNode.setLineOfCode(ctx.lastFormalParameterArg().getStart().getLine());
//                argNode.setCode(getOriginalCodeText(ctx.lastFormalParameterArg()));
//                AST.addVertex(argNode);
//                AST.addEdge(parentStack.peek(), argNode);
//            }
//            parentStack.pop();
//            return null;
//        }

//        @Override
//        public String visitFormalParameterArg(JavaScriptParser.FormalParameterArgContext ctx) {
//            //    : assignable ('=' singleExpression)?      // ECMAScript 6: Initialization
//            if (ctx == null || ctx.isEmpty()) {
//                return "";
//            }
//            return visitAssignable(ctx.assignable()) + "=" + visit(ctx.singleExpression());
//        }

//        @Override
//        public String visitLastFormalParameterArg(JavaScriptParser.LastFormalParameterArgContext ctx) {
//            if (ctx == null || ctx.isEmpty()) {
//                return "";
//            }
//            return ctx.Ellipsis().getText() + " " + visit(ctx.singleExpression());
//        }


//        @Override
//        public String visitFunctionBody(JavaScriptParser.FunctionBodyContext ctx) {
//            if (ctx == null || ctx.isEmpty()) {
//                return "";
//            }
//            return "{" + visitSourceElements(ctx.sourceElements()) + "}";
//        }


        @Override
        public String visitArrayLiteral(JavaScriptParser.ArrayLiteralContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            ASNode arrayNode = new ASNode(ASNode.Type.ARRAY);
            arrayNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(arrayNode);
            AST.addEdge(parentStack.peek(), arrayNode);
            parentStack.push(arrayNode);
            for (JavaScriptParser.ArrayElementContext arrayElementContext : ctx.elementList().arrayElement()) {
                visit(arrayElementContext);
            }
            parentStack.pop();
            return null;
        }

//        @Override
//        public String visitElementList(JavaScriptParser.ElementListContext ctx) {
//            //TODO
//            if (ctx == null || ctx.isEmpty()) {
//                return "";
//            }
//            StringBuffer stringBuffer = new StringBuffer();
//            stringBuffer.append(",");
//            stringBuffer.append(visitArrayElement(ctx.arrayElement(0)));
//            stringBuffer.append(",");
//            for (int i = 1; i < ctx.arrayElement().size(); i++) {
//                stringBuffer.append(",");
//                stringBuffer.append(visitArrayElement(ctx.arrayElement(i)));
//            }
//            stringBuffer.append(",");
//            return stringBuffer.toString();
//        }

//        @Override
//        public String visitArrayElement(JavaScriptParser.ArrayElementContext ctx) {
//            if (ctx == null || ctx.isEmpty()) {
//                return "";
//            }
//            if (ctx.Ellipsis() != null) {
//                return ctx.Ellipsis().getText() + " " + visit(ctx.singleExpression());
//            }
//            return visit(ctx.singleExpression());
//        }

        @Override
        public String visitPropertyExpressionAssignment(JavaScriptParser.PropertyExpressionAssignmentContext ctx) {
            //     : propertyName ':' singleExpression                                             # PropertyExpressionAssignment
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }

            // property name
            ASNode propName = new ASNode(ASNode.Type.NAME);
            propName.setLineOfCode(ctx.propertyName().getStart().getLine());
            propName.setCode(getOriginalCodeText(ctx.propertyName()));
            AST.addVertex(propName);
            AST.addEdge(parentStack.peek(), propName);

            // single expression
            ASNode expr = new ASNode(ASNode.Type.EXPRESSION);
            expr.setLineOfCode(ctx.singleExpression().getStart().getLine());
            AST.addVertex(expr);
            AST.addEdge(parentStack.peek(), expr);
            parentStack.push(expr);
            visit(ctx.singleExpression());
            parentStack.pop();

            return null;
//            return visitPropertyName(ctx.propertyName()) + ":" + visit(ctx.singleExpression());
        }

        @Override
        public String visitComputedPropertyExpressionAssignment(JavaScriptParser.ComputedPropertyExpressionAssignmentContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            return "[" + visit(ctx.singleExpression(0)) + "]:" + visit(ctx.singleExpression(1));
        }

        @Override
        public String visitFunctionProperty(JavaScriptParser.FunctionPropertyContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            //TODO
            StringBuffer stringBuffer = new StringBuffer();
            if (ctx.Async() != null) {
                stringBuffer.append(ctx.Async());
            }
            stringBuffer.append("*");
            stringBuffer.append(visitPropertyName(ctx.propertyName()));
            stringBuffer.append("(");
            if (ctx.formalParameterList() != null && !ctx.formalParameterList().isEmpty()) {
                stringBuffer.append(visitFormalParameterList(ctx.formalParameterList()));
            }
            stringBuffer.append(")");
            stringBuffer.append(visitFunctionBody(ctx.functionBody()));
            return stringBuffer.toString();
        }

        @Override
        public String visitPropertyGetter(JavaScriptParser.PropertyGetterContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            return visitGetter(ctx.getter()) + "()" + visitFunctionBody(ctx.functionBody());
        }

        @Override
        public String visitPropertySetter(JavaScriptParser.PropertySetterContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            return visitSetter(ctx.setter()) + "(" + visitFormalParameterArg(ctx.formalParameterArg()) + ")" + visitFunctionBody(ctx.functionBody());
        }

        @Override
        public String visitPropertyShorthand(JavaScriptParser.PropertyShorthandContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            if (ctx.Ellipsis() != null) {
                return ctx.Ellipsis().getText() + " " + visit(ctx.singleExpression());
            }
            return visit(ctx.singleExpression());
        }

        @Override
        public String visitPropertyName(JavaScriptParser.PropertyNameContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            if (ctx.identifierName() != null) {
                ASNode node = new ASNode(ASNode.Type.STATEMENT);
                node.setLineOfCode(ctx.identifierName().getStart().getLine());
                node.setCode(getOriginalCodeText(ctx.identifierName()));
                AST.addVertex(node);
                AST.addEdge(parentStack.peek(), node);
            }
            if (ctx.numericLiteral() != null) {
                ASNode node = new ASNode(ASNode.Type.STATEMENT);
                node.setLineOfCode(ctx.numericLiteral().getStart().getLine());
                node.setCode(getOriginalCodeText(ctx.numericLiteral()));
                AST.addVertex(node);
                AST.addEdge(parentStack.peek(), node);
            }
            if (ctx.StringLiteral() != null) {
                ASNode node = new ASNode(ASNode.Type.STATEMENT);
                node.setLineOfCode(0);
                node.setCode(ctx.StringLiteral().getText());
                AST.addVertex(node);
                AST.addEdge(parentStack.peek(), node);
            }
            if (ctx.singleExpression() != null) {
                ASNode node = new ASNode(ASNode.Type.STATEMENT);
                node.setLineOfCode(ctx.singleExpression().getStart().getLine());
                node.setCode("[" + getOriginalCodeText(ctx.singleExpression()) + "]");
                AST.addVertex(node);
                AST.addEdge(parentStack.peek(), node);
            }
            return "";
        }

//        @Override
//        public String visitArguments(JavaScriptParser.ArgumentsContext ctx) {
//            if (ctx == null || ctx.isEmpty()) {
//                return "";
//            }
//            ASNode argsNode = new ASNode(ASNode.Type.ARGUMENTS);
//            argsNode.setLineOfCode(ctx.getStart().getLine());
//            AST.addVertex(argsNode);
//            AST.addEdge(parentStack.peek(), argsNode);
//            parentStack.push(argsNode);
//            for (JavaScriptParser.ArgumentContext argumentContext : ctx.argument()) {
//                visit(argumentContext);
//            }
//            parentStack.pop();
//            return null;
//        }

        @Override
        public String visitArgument(JavaScriptParser.ArgumentContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            if (ctx.identifier() == null || ctx.identifier().isEmpty()) {
                // single expression
                visit(ctx.singleExpression());
            } else {
                // identifier
                ASNode idNode = new ASNode(ASNode.Type.IDENTIFIER);
                idNode.setLineOfCode(ctx.identifier().getStart().getLine());
                idNode.setCode(getOriginalCodeText(ctx.identifier()));
                AST.addVertex(idNode);
                AST.addEdge(parentStack.peek(), idNode);
            }
            return null;
        }

        @Override
        public String visitExpressionSequence(JavaScriptParser.ExpressionSequenceContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            for (JavaScriptParser.SingleExpressionContext singleExpressionContext : ctx.singleExpression()) {
                visit(singleExpressionContext);
            }
            return null;
        }

        // todo
        @Override
        public String visitTemplateStringExpression(JavaScriptParser.TemplateStringExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            return visit(ctx.singleExpression()) + " " + visitTemplateStringLiteral(ctx.templateStringLiteral());
        }

        @Override
        public String visitTernaryExpression(JavaScriptParser.TernaryExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            return visit(ctx.singleExpression(0)) + "?" + visit(ctx.singleExpression(1)) + ":" + visit(ctx.singleExpression(2));
        }

        @Override
        public String visitLogicalAndExpression(JavaScriptParser.LogicalAndExpressionContext ctx) {
            // exprBitAnd :  expression '&&' expression
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            // operator
            ASNode opNode = new ASNode(ASNode.Type.OPERATOR);
            opNode.setLineOfCode(0);
            opNode.setCode("&&");
            AST.addVertex(opNode);
            AST.addEdge(parentStack.peek(), opNode);
            parentStack.push(opNode);

            for (JavaScriptParser.SingleExpressionContext singleExpressionContext : ctx.singleExpression()) {
                if (singleExpressionContext.getChildCount() == 1) {
                    ASNode tokenNode;
                    tokenNode = vars.get(getOriginalCodeText(singleExpressionContext)) != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                    tokenNode.setLineOfCode(singleExpressionContext.getStart().getLine());
                    tokenNode.setCode(getOriginalCodeText(singleExpressionContext));
                    AST.addVertex(tokenNode);
                    AST.addEdge(parentStack.peek(), tokenNode);
                } else {
                    // expression
                    visit(singleExpressionContext);
                }
            }
            parentStack.pop();
            return null;
        }

        @Override
        public String visitPowerExpression(JavaScriptParser.PowerExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            // operator
            ASNode opNode = new ASNode(ASNode.Type.OPERATOR);
            opNode.setLineOfCode(0);
            opNode.setCode("**");
            AST.addVertex(opNode);
            AST.addEdge(parentStack.peek(), opNode);
            parentStack.push(opNode);

            for (JavaScriptParser.SingleExpressionContext singleExpressionContext : ctx.singleExpression()) {
                if (singleExpressionContext.getChildCount() == 1) {
                    ASNode tokenNode;
                    tokenNode = vars.get(getOriginalCodeText(singleExpressionContext)) != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                    tokenNode.setLineOfCode(singleExpressionContext.getStart().getLine());
                    tokenNode.setCode(getOriginalCodeText(singleExpressionContext));
                    AST.addVertex(tokenNode);
                    AST.addEdge(parentStack.peek(), tokenNode);
                } else {
                    // expression
                    visit(singleExpressionContext);
                }
            }
            parentStack.pop();
            return null;
        }

        @Override
        public String visitPreIncrementExpression(JavaScriptParser.PreIncrementExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            ASNode opNode = new ASNode(ASNode.Type.PREFIX_UNARY_OPERATION);
            opNode.setLineOfCode(ctx.getStart().getLine());
            opNode.setCode("++");
            AST.addVertex(opNode);
            AST.addEdge(parentStack.peek(), opNode);
            parentStack.push(opNode);

            if (ctx.singleExpression().getChildCount() == 1) {
                ASNode varNode = new ASNode(ASNode.Type.VARIABLE);
                varNode.setLineOfCode(0);
                varNode.setCode(getOriginalCodeText(ctx.singleExpression()));
                AST.addVertex(varNode);
                AST.addEdge(parentStack.peek(), varNode);
            } else {
                visit(ctx.singleExpression());
            }

            parentStack.pop();
            return null;
        }

        @Override
        public String visitObjectLiteralExpression(JavaScriptParser.ObjectLiteralExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            visit(ctx.objectLiteral());
            return null;
        }

        @Override
        public String visitMetaExpression(JavaScriptParser.MetaExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            return ctx.New().getText() + "." + visitIdentifier(ctx.identifier());
        }

        @Override
        public String visitInExpression(JavaScriptParser.InExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            // operator
            ASNode opNode = new ASNode(ASNode.Type.OPERATOR);
            opNode.setLineOfCode(0);
            opNode.setCode("in");
            AST.addVertex(opNode);
            AST.addEdge(parentStack.peek(), opNode);
            parentStack.push(opNode);

            for (JavaScriptParser.SingleExpressionContext singleExpressionContext : ctx.singleExpression()) {
                if (singleExpressionContext.getChildCount() == 1) {
                    ASNode tokenNode;
                    tokenNode = vars.get(getOriginalCodeText(singleExpressionContext)) != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                    tokenNode.setLineOfCode(singleExpressionContext.getStart().getLine());
                    tokenNode.setCode(getOriginalCodeText(singleExpressionContext));
                    AST.addVertex(tokenNode);
                    AST.addEdge(parentStack.peek(), tokenNode);
                } else {
                    // expression
                    visit(singleExpressionContext);
                }
            }
            parentStack.pop();
            return null;
        }

        @Override
        public String visitLogicalOrExpression(JavaScriptParser.LogicalOrExpressionContext ctx) {
            // exprBitAnd :  expression '||' expression
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            // operator
            ASNode opNode = new ASNode(ASNode.Type.OPERATOR);
            opNode.setLineOfCode(0);
            opNode.setCode("||");
            AST.addVertex(opNode);
            AST.addEdge(parentStack.peek(), opNode);
            parentStack.push(opNode);

            for (JavaScriptParser.SingleExpressionContext singleExpressionContext : ctx.singleExpression()) {
                if (singleExpressionContext.getChildCount() == 1) {
                    ASNode tokenNode;
                    tokenNode = vars.get(getOriginalCodeText(singleExpressionContext)) != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                    tokenNode.setLineOfCode(singleExpressionContext.getStart().getLine());
                    tokenNode.setCode(getOriginalCodeText(singleExpressionContext));
                    AST.addVertex(tokenNode);
                    AST.addEdge(parentStack.peek(), tokenNode);
                } else {
                    // expression
                    visit(singleExpressionContext);
                }
            }
            parentStack.pop();
            return null;
        }

        @Override
        public String visitOptionalChainExpression(JavaScriptParser.OptionalChainExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            // operator
            ASNode opNode = new ASNode(ASNode.Type.OPERATOR);
            opNode.setLineOfCode(0);
            opNode.setCode("?.");
            AST.addVertex(opNode);
            AST.addEdge(parentStack.peek(), opNode);
            parentStack.push(opNode);

            for (JavaScriptParser.SingleExpressionContext singleExpressionContext : ctx.singleExpression()) {
                if (singleExpressionContext.getChildCount() == 1) {
                    ASNode tokenNode;
                    tokenNode = vars.get(getOriginalCodeText(singleExpressionContext)) != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                    tokenNode.setLineOfCode(singleExpressionContext.getStart().getLine());
                    tokenNode.setCode(getOriginalCodeText(singleExpressionContext));
                    AST.addVertex(tokenNode);
                    AST.addEdge(parentStack.peek(), tokenNode);
                } else {
                    // expression
                    visit(singleExpressionContext);
                }
            }
            parentStack.pop();
            return null;
        }

        @Override
        public String visitNotExpression(JavaScriptParser.NotExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            ASNode opNode = new ASNode(ASNode.Type.OPERATOR);
            opNode.setLineOfCode(0);
            opNode.setCode("!");
            AST.addVertex(opNode);
            AST.addEdge(parentStack.peek(), opNode);

            JavaScriptParser.SingleExpressionContext singleExpressionContext = ctx.singleExpression();
            if (singleExpressionContext.getChildCount() == 1) {
                // token
                ASNode tokenNode;
                tokenNode = vars.get(getOriginalCodeText(ctx)) != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                tokenNode.setLineOfCode(ctx.getStart().getLine());
                tokenNode.setCode(getOriginalCodeText(ctx));
                AST.addVertex(tokenNode);
                AST.addEdge(parentStack.peek(), tokenNode);
            } else {
                // expression
                visit(singleExpressionContext);
            }
            parentStack.pop();

            return null;
        }

        @Override
        public String visitPreDecreaseExpression(JavaScriptParser.PreDecreaseExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            ASNode opNode = new ASNode(ASNode.Type.PREFIX_UNARY_OPERATION);
            opNode.setLineOfCode(ctx.getStart().getLine());
            opNode.setCode("--");
            AST.addVertex(opNode);
            AST.addEdge(parentStack.peek(), opNode);
            parentStack.push(opNode);

            if (ctx.singleExpression().getChildCount() == 1) {
                ASNode varNode = new ASNode(ASNode.Type.VARIABLE);
                varNode.setLineOfCode(0);
                varNode.setCode(getOriginalCodeText(ctx.singleExpression()));
                AST.addVertex(varNode);
                AST.addEdge(parentStack.peek(), varNode);
            } else {
                visit(ctx.singleExpression());
            }

            parentStack.pop();
            return null;
        }

        @Override
        public String visitArgumentsExpression(JavaScriptParser.ArgumentsExpressionContext ctx) {
            //    | singleExpression arguments                                            # ArgumentsExpression
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            ASNode funcNode = new ASNode(ASNode.Type.FUNCTION_CALL);
            funcNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(funcNode);
            AST.addEdge(parentStack.peek(), funcNode);
            parentStack.push(funcNode);

            // function name
            if (ctx.singleExpression() != null) {
                ASNode funcNameNode = new ASNode(ASNode.Type.FUNC_NAME);
                funcNameNode.setLineOfCode(ctx.singleExpression().getStart().getLine());
                AST.addVertex(funcNameNode);
                AST.addEdge(parentStack.peek(), funcNameNode);
                parentStack.push(funcNameNode);
                visit(ctx.singleExpression());
                parentStack.pop();
            }

            // function params
            if (ctx.arguments() != null) {
                ASNode paramsNode = new ASNode(ASNode.Type.PARAMS);
                paramsNode.setLineOfCode(ctx.arguments().getStart().getLine());
                AST.addVertex(paramsNode);
                AST.addEdge(parentStack.peek(), paramsNode);
                parentStack.push(paramsNode);
                visit(ctx.arguments());
                parentStack.pop();
            }

            parentStack.pop();
            return null;
        }

        @Override
        public String visitAwaitExpression(JavaScriptParser.AwaitExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            return ctx.Await().getText() + " " + visit(ctx.singleExpression());
        }

        @Override
        public String visitThisExpression(JavaScriptParser.ThisExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            return ctx.This().getText();
        }

        @Override
        public String visitFunctionExpression(JavaScriptParser.FunctionExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            return ctx.anonymousFunction().getText();
        }

        @Override
        public String visitUnaryMinusExpression(JavaScriptParser.UnaryMinusExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            ASNode opNode = new ASNode(ASNode.Type.OPERATOR);
            opNode.setLineOfCode(0);
            opNode.setCode("-");
            AST.addVertex(opNode);
            AST.addEdge(parentStack.peek(), opNode);

            JavaScriptParser.SingleExpressionContext singleExpressionContext = ctx.singleExpression();
            if (singleExpressionContext.getChildCount() == 1) {
                // token
                ASNode tokenNode;
                tokenNode = vars.get(getOriginalCodeText(singleExpressionContext)) != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                tokenNode.setLineOfCode(singleExpressionContext.getStart().getLine());
                tokenNode.setCode(getOriginalCodeText(singleExpressionContext));
                AST.addVertex(tokenNode);
                AST.addEdge(parentStack.peek(), tokenNode);
            } else {
                // expression
                visit(singleExpressionContext);
            }
            parentStack.pop();

            return null;
        }

        @Override
        public String visitAssignmentExpression(JavaScriptParser.AssignmentExpressionContext ctx) {
            // exprAssignment :  expression  ( '='  | '+='  | '-='   | '*='  | '/=' | '&=' |
            //                                 '|=' | '^=' | '>>=' | '>>>=' | '<<=' | '%=' )  expression
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            // assignment operator
            ASNode assignNode = new ASNode(ASNode.Type.ASSIGNMENT);
            assignNode.setLineOfCode(ctx.getStart().getLine());
            assignNode.setCode(ctx.getChild(1).getText());
            AST.addVertex(assignNode);
            AST.addEdge(parentStack.peek(), assignNode);
            parentStack.push(assignNode);

            for (JavaScriptParser.SingleExpressionContext singleExpressionContext : ctx.singleExpression()) {
                if (singleExpressionContext.getChildCount() == 1) {
                    ASNode tokenNode;
                    tokenNode = vars.get(getOriginalCodeText(singleExpressionContext)) != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                    tokenNode.setLineOfCode(singleExpressionContext.getStart().getLine());
                    tokenNode.setCode(getOriginalCodeText(singleExpressionContext));
                    AST.addVertex(tokenNode);
                    AST.addEdge(parentStack.peek(), tokenNode);
                } else {
                    // expression
                    visit(singleExpressionContext);
                }
            }
            parentStack.pop();
            return null;
        }

        @Override
        public String visitPostDecreaseExpression(JavaScriptParser.PostDecreaseExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            ASNode opNode = new ASNode(ASNode.Type.PREFIX_UNARY_OPERATION);
            String op = "--";
            opNode.setLineOfCode(ctx.getStart().getLine());
            opNode.setCode(op);
            AST.addVertex(opNode);
            AST.addEdge(parentStack.peek(), opNode);
            parentStack.push(opNode);

            if (ctx.singleExpression().getChildCount() == 1) {
                ASNode varNode = new ASNode(ASNode.Type.VARIABLE);
                varNode.setLineOfCode(0);
                varNode.setCode(getOriginalCodeText(ctx.singleExpression()));
                AST.addVertex(varNode);
                AST.addEdge(parentStack.peek(), varNode);
            } else {
                visit(ctx.singleExpression());
            }

            parentStack.pop();

            return null;
        }

        @Override
        public String visitTypeofExpression(JavaScriptParser.TypeofExpressionContext ctx) {
            // exprTypeOf: typeof(expression)
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            //    | Delete singleExpression
            ASNode typeofNode = new ASNode(ASNode.Type.TYPE_OF);
            typeofNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(typeofNode);
            AST.addEdge(parentStack.peek(), typeofNode);
            ASNode block = new ASNode(ASNode.Type.BLOCK);
            block.setLineOfCode(ctx.singleExpression().getStart().getLine());
            AST.addVertex(block);
            AST.addEdge(typeofNode, block);
            parentStack.push(block);
            visit(ctx.singleExpression());
            parentStack.pop();
            return "";
        }

        @Override
        public String visitInstanceofExpression(JavaScriptParser.InstanceofExpressionContext ctx) {
            // exprInstanceOf :  expression 'instanceof' typeType
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            return visit(ctx.singleExpression(0)) + " instanceof " + getOriginalCodeText(ctx.singleExpression(1));
        }


        @Override
        public String visitUnaryPlusExpression(JavaScriptParser.UnaryPlusExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            ASNode opNode = new ASNode(ASNode.Type.OPERATOR);
            opNode.setLineOfCode(0);
            opNode.setCode("+");
            AST.addVertex(opNode);
            AST.addEdge(parentStack.peek(), opNode);

            JavaScriptParser.SingleExpressionContext singleExpressionContext = ctx.singleExpression();
            if (singleExpressionContext.getChildCount() == 1) {
                // token
                ASNode tokenNode;
                tokenNode = vars.get(getOriginalCodeText(ctx)) != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                tokenNode.setLineOfCode(ctx.getStart().getLine());
                tokenNode.setCode(getOriginalCodeText(ctx));
                AST.addVertex(tokenNode);
                AST.addEdge(parentStack.peek(), tokenNode);
            } else {
                // expression
                visit(singleExpressionContext);
            }
            parentStack.pop();

            return null;
        }

        @Override
        public String visitDeleteExpression(JavaScriptParser.DeleteExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            //    | Delete singleExpression
            ASNode delNode = new ASNode(ASNode.Type.DELETE);
            delNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(delNode);
            AST.addEdge(parentStack.peek(), delNode);
            ASNode block = new ASNode(ASNode.Type.BLOCK);
            block.setLineOfCode(ctx.singleExpression().getStart().getLine());
            AST.addVertex(block);
            AST.addEdge(delNode, block);
            parentStack.push(block);
            visit(ctx.singleExpression());
            parentStack.pop();
            return "";
        }

        @Override
        public String visitImportExpression(JavaScriptParser.ImportExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            ASNode importNode = new ASNode(ASNode.Type.IMPORT);
            importNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(importNode);
            AST.addEdge(parentStack.peek(), importNode);
            parentStack.push(importNode);
            visit(ctx.singleExpression());
            parentStack.pop();

            return null;
        }

        @Override
        public String visitEqualityExpression(JavaScriptParser.EqualityExpressionContext ctx) {
            //     | singleExpression ('==' | '!=' | '===' | '!==') singleExpression       # EqualityExpression
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            // operator
            ASNode opNode = new ASNode(ASNode.Type.OPERATOR);
            opNode.setLineOfCode(0);
            opNode.setCode(ctx.getChild(1).getText());
            AST.addVertex(opNode);
            AST.addEdge(parentStack.peek(), opNode);
            parentStack.push(opNode);

            for (JavaScriptParser.SingleExpressionContext singleExpressionContext : ctx.singleExpression()) {
                if (singleExpressionContext.getChildCount() == 1) {
                    ASNode tokenNode;
                    tokenNode = vars.get(getOriginalCodeText(singleExpressionContext)) != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                    tokenNode.setLineOfCode(singleExpressionContext.getStart().getLine());
                    tokenNode.setCode(getOriginalCodeText(singleExpressionContext));
                    AST.addVertex(tokenNode);
                    AST.addEdge(parentStack.peek(), tokenNode);
                } else {
                    // expression
                    visit(singleExpressionContext);
                }
            }
            parentStack.pop();
            return null;
        }

        @Override
        public String visitBitXOrExpression(JavaScriptParser.BitXOrExpressionContext ctx) {
            // exprBitAnd :  expression '^' expression
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            // operator
            ASNode opNode = new ASNode(ASNode.Type.OPERATOR);
            opNode.setLineOfCode(0);
            opNode.setCode("^");
            AST.addVertex(opNode);
            AST.addEdge(parentStack.peek(), opNode);
            parentStack.push(opNode);

            for (JavaScriptParser.SingleExpressionContext singleExpressionContext : ctx.singleExpression()) {
                if (singleExpressionContext.getChildCount() == 1) {
                    ASNode tokenNode;
                    tokenNode = vars.get(getOriginalCodeText(singleExpressionContext)) != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                    tokenNode.setLineOfCode(singleExpressionContext.getStart().getLine());
                    tokenNode.setCode(getOriginalCodeText(singleExpressionContext));
                    AST.addVertex(tokenNode);
                    AST.addEdge(parentStack.peek(), tokenNode);
                } else {
                    // expression
                    visit(singleExpressionContext);
                }
            }
            parentStack.pop();
            return null;
        }

        @Override
        public String visitSuperExpression(JavaScriptParser.SuperExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            return ctx.Super().getText();
        }

        @Override
        public String visitMultiplicativeExpression(JavaScriptParser.MultiplicativeExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            // operator
            ASNode opNode = new ASNode(ASNode.Type.OPERATOR);
            opNode.setLineOfCode(ctx.getStart().getLine());
            opNode.setCode(ctx.getChild(1).getText());
            AST.addVertex(opNode);
            AST.addEdge(parentStack.peek(), opNode);
            parentStack.push(opNode);

            for (JavaScriptParser.SingleExpressionContext singleExpressionContext : ctx.singleExpression()) {
                if (singleExpressionContext.getChildCount() == 1) {
                    ASNode tokenNode;
                    tokenNode = vars.get(getOriginalCodeText(singleExpressionContext)) != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                    tokenNode.setLineOfCode(singleExpressionContext.getStart().getLine());
                    tokenNode.setCode(getOriginalCodeText(singleExpressionContext));
                    AST.addVertex(tokenNode);
                    AST.addEdge(parentStack.peek(), tokenNode);
                } else {
                    // expression
                    visit(singleExpressionContext);
                }
            }
            parentStack.pop();
            return null;
        }

        @Override
        public String visitBitShiftExpression(JavaScriptParser.BitShiftExpressionContext ctx) {
            // | singleExpression ('<<' | '>>' | '>>>') singleExpression               # BitShiftExpression
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            String sub = ctx.getText().substring(ctx.singleExpression(0).getText().length());
            String op;
            if (sub.startsWith(">>>"))
                op = ">>>";
            else
                op = sub.substring(0, 2);

            // operator
            ASNode opNode = new ASNode(ASNode.Type.OPERATOR);
            opNode.setLineOfCode(0);
            opNode.setCode(op);
            AST.addVertex(opNode);
            AST.addEdge(parentStack.peek(), opNode);
            parentStack.push(opNode);

            for (JavaScriptParser.SingleExpressionContext singleExpressionContext : ctx.singleExpression()) {
                if (singleExpressionContext.getChildCount() == 1) {
                    ASNode tokenNode;
                    tokenNode = vars.get(getOriginalCodeText(singleExpressionContext)) != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                    tokenNode.setLineOfCode(singleExpressionContext.getStart().getLine());
                    tokenNode.setCode(getOriginalCodeText(singleExpressionContext));
                    AST.addVertex(tokenNode);
                    AST.addEdge(parentStack.peek(), tokenNode);
                } else {
                    // expression
                    visit(singleExpressionContext);
                }
            }
            parentStack.pop();

            return null;
        }

        @Override
        public String visitParenthesizedExpression(JavaScriptParser.ParenthesizedExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            ASNode asNode = new ASNode(ASNode.Type.PARENTHESIS);
            asNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(asNode);
            AST.addEdge(parentStack.peek(), asNode);
            parentStack.push(asNode);
            for (JavaScriptParser.SingleExpressionContext singleExpressionContext : ctx.expressionSequence().singleExpression()) {
                visit(singleExpressionContext);
            }
            parentStack.pop();

            return null;
        }

        @Override
        public String visitAdditiveExpression(JavaScriptParser.AdditiveExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            // operator
            ASNode opNode = new ASNode(ASNode.Type.OPERATOR);
            opNode.setLineOfCode(ctx.getStart().getLine());
            opNode.setCode(ctx.getChild(1).getText());
            AST.addVertex(opNode);
            AST.addEdge(parentStack.peek(), opNode);
            parentStack.push(opNode);

            for (JavaScriptParser.SingleExpressionContext singleExpressionContext : ctx.singleExpression()) {
                if (singleExpressionContext.getChildCount() == 1) {
                    ASNode tokenNode;
                    tokenNode = vars.get(getOriginalCodeText(singleExpressionContext)) != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                    tokenNode.setLineOfCode(singleExpressionContext.getStart().getLine());
                    tokenNode.setCode(getOriginalCodeText(singleExpressionContext));
                    AST.addVertex(tokenNode);
                    AST.addEdge(parentStack.peek(), tokenNode);
                } else {
                    // expression
                    visit(singleExpressionContext);
                }
            }
            parentStack.pop();
            return null;
        }

        @Override
        public String visitRelationalExpression(JavaScriptParser.RelationalExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            // operator
            ASNode opNode = new ASNode(ASNode.Type.OPERATOR);
            opNode.setLineOfCode(ctx.getStart().getLine());
            opNode.setCode(ctx.getChild(1).getText());
            AST.addVertex(opNode);
            AST.addEdge(parentStack.peek(), opNode);
            parentStack.push(opNode);

            for (JavaScriptParser.SingleExpressionContext singleExpressionContext : ctx.singleExpression()) {
                if (singleExpressionContext.getChildCount() == 1) {
                    ASNode tokenNode;
                    tokenNode = vars.get(getOriginalCodeText(singleExpressionContext)) != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                    tokenNode.setLineOfCode(singleExpressionContext.getStart().getLine());
                    tokenNode.setCode(getOriginalCodeText(singleExpressionContext));
                    AST.addVertex(tokenNode);
                    AST.addEdge(parentStack.peek(), tokenNode);
                } else {
                    // expression
                    visit(singleExpressionContext);
                }
            }
            parentStack.pop();
            return null;
        }

        @Override
        public String visitPostIncrementExpression(JavaScriptParser.PostIncrementExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            ASNode opNode = new ASNode(ASNode.Type.PREFIX_UNARY_OPERATION);
            String op = "++";
            opNode.setLineOfCode(ctx.getStart().getLine());
            opNode.setCode(op);
            AST.addVertex(opNode);
            AST.addEdge(parentStack.peek(), opNode);
            parentStack.push(opNode);

            if (ctx.singleExpression().getChildCount() == 1) {
                ASNode varNode = new ASNode(ASNode.Type.VARIABLE);
                varNode.setLineOfCode(0);
                varNode.setCode(getOriginalCodeText(ctx.singleExpression()));
                AST.addVertex(varNode);
                AST.addEdge(parentStack.peek(), varNode);
            } else {
                visit(ctx.singleExpression());
            }

            parentStack.pop();

            return null;
        }

        @Override
        public String visitYieldExpression(JavaScriptParser.YieldExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            return visitYieldStatement(ctx.yieldStatement());
        }

        @Override
        public String visitBitNotExpression(JavaScriptParser.BitNotExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            ASNode opNode = new ASNode(ASNode.Type.PREFIX_UNARY_OPERATION);
            String op = "~";
            opNode.setLineOfCode(ctx.getStart().getLine());
            opNode.setCode(op);
            AST.addVertex(opNode);
            AST.addEdge(parentStack.peek(), opNode);
            parentStack.push(opNode);

            if (ctx.singleExpression().getChildCount() == 1) {
                ASNode varNode = new ASNode(ASNode.Type.VARIABLE);
                varNode.setLineOfCode(0);
                varNode.setCode(getOriginalCodeText(ctx.singleExpression()));
                AST.addVertex(varNode);
                AST.addEdge(parentStack.peek(), varNode);
            } else {
                visit(ctx.singleExpression());
            }

            parentStack.pop();

            return null;
        }

        @Override
        public String visitNewExpression(JavaScriptParser.NewExpressionContext ctx) {
            //    | New singleExpression arguments                                        # NewExpression
            //    | New singleExpression                                                  # NewExpression

            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            ASNode newNode = new ASNode(ASNode.Type.NEW);
            newNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(newNode);
            AST.addEdge(parentStack.peek(), newNode);
            ASNode block = new ASNode(ASNode.Type.BLOCK);
            block.setLineOfCode(ctx.singleExpression().getStart().getLine());
            AST.addVertex(block);
            AST.addEdge(newNode, block);
            parentStack.push(block);
            visit(ctx.singleExpression());

            if (ctx.arguments() != null && !ctx.arguments().isEmpty()) {
                ASNode argsNode = new ASNode(ASNode.Type.ARGUMENTS);
                argsNode.setLineOfCode(ctx.getStart().getLine());
                AST.addVertex(argsNode);
                AST.addEdge(parentStack.peek(), argsNode);
                parentStack.push(argsNode);
                for (JavaScriptParser.ArgumentContext argumentContext : ctx.arguments().argument()) {
                    visit(argumentContext);
                }
                parentStack.pop();
            }

            parentStack.pop();
            return "";
        }

//        @Override
//        public String visitLiteralExpression(JavaScriptParser.LiteralExpressionContext ctx) {
//            if (ctx == null || ctx.isEmpty()) {
//                return "";
//            }
//            return visitLiteral(ctx.literal());
//        }

//        @Override
//        public String visitArrayLiteralExpression(JavaScriptParser.ArrayLiteralExpressionContext ctx) {
//            if (ctx == null || ctx.isEmpty()) {
//                return "";
//            }
//            return visitArrayLiteral(ctx.arrayLiteral());
//        }

        //TODO 待确认
        @Override
        public String visitMemberDotExpression(JavaScriptParser.MemberDotExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            //     | singleExpression '?'? '.' '#'? identifierName                         # MemberDotExpression

            // dot
            ASNode callExpr = new ASNode(ASNode.Type.MEMBER_ACCESS);
            callExpr.setLineOfCode(ctx.getStart().getLine());
            callExpr.setCode(".");
            AST.addVertex(callExpr);
            AST.addEdge(parentStack.peek(), callExpr);
            parentStack.push(callExpr);

            ASNode scopeNode = new ASNode(ASNode.Type.SCOPE);
            scopeNode.setLineOfCode(ctx.singleExpression().getStart().getLine());
            AST.addVertex(scopeNode);
            AST.addEdge(callExpr, scopeNode);
            // scope
            parentStack.push(scopeNode);
            visit(ctx.singleExpression());
            parentStack.pop();

            // name
            ASNode nameNode = new ASNode(ASNode.Type.NAME);
            nameNode.setLineOfCode(0);
            nameNode.setCode(ctx.identifierName().getText());
            AST.addVertex(nameNode);
            AST.addEdge(parentStack.peek(), nameNode);
            parentStack.push(nameNode);
            visit(ctx.identifierName());
            parentStack.pop();

            parentStack.pop();

            return null;
        }

//        @Override
//        public String visitClassExpression(JavaScriptParser.ClassExpressionContext ctx) {
//            if (ctx == null || ctx.isEmpty()) {
//                return "";
//            }
//            if (ctx.identifier() != null) {
//                return ctx.Class().getText() + " " + visitIdentifier(ctx.identifier());
//            }
//            return ctx.Class().getText() + " " + visitClassTail(ctx.classTail());
//        }

        @Override
        public String visitMemberIndexExpression(JavaScriptParser.MemberIndexExpressionContext ctx) {
            // | singleExpression '?.'? '[' expressionSequence ']'                     # MemberIndexExpression
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            // operator
            ASNode opNode = new ASNode(ASNode.Type.OPERATOR);
            opNode.setLineOfCode(0);
            opNode.setCode("?.");
            AST.addVertex(opNode);
            AST.addEdge(parentStack.peek(), opNode);
            parentStack.push(opNode);

            JavaScriptParser.SingleExpressionContext singleExpressionContext = ctx.singleExpression();
            if (singleExpressionContext.getChildCount() == 1) {
                ASNode tokenNode;
                tokenNode = vars.get(getOriginalCodeText(singleExpressionContext)) != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                tokenNode.setLineOfCode(singleExpressionContext.getStart().getLine());
                tokenNode.setCode(getOriginalCodeText(singleExpressionContext));
                AST.addVertex(tokenNode);
                AST.addEdge(parentStack.peek(), tokenNode);
            } else {
                // expression
                visit(singleExpressionContext);
            }

            ASNode idxNode = new ASNode(ASNode.Type.MEMBER_INDEX);
            AST.addVertex(idxNode);
            AST.addEdge(parentStack.peek(), idxNode);
            parentStack.push(idxNode);
            for (JavaScriptParser.SingleExpressionContext expressionContext : ctx.expressionSequence().singleExpression()) {
                visit(expressionContext);
            }
            parentStack.pop();

            parentStack.pop();
            return null;
        }

        @Override
        public String visitIdentifierExpression(JavaScriptParser.IdentifierExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            ASNode idNode = new ASNode(ASNode.Type.IDENTIFIER);
            idNode.setLineOfCode(ctx.getStart().getLine());
            idNode.setCode(ctx.identifier().getText());
            AST.addVertex(idNode);
            AST.addEdge(parentStack.peek(), idNode);
            return null;
        }

        @Override
        public String visitBitAndExpression(JavaScriptParser.BitAndExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            // operator
            ASNode opNode = new ASNode(ASNode.Type.OPERATOR);
            opNode.setLineOfCode(0);
            opNode.setCode("&");
            AST.addVertex(opNode);
            AST.addEdge(parentStack.peek(), opNode);
            parentStack.push(opNode);

            for (JavaScriptParser.SingleExpressionContext singleExpressionContext : ctx.singleExpression()) {
                if (singleExpressionContext.getChildCount() == 1) {
                    ASNode tokenNode;
                    tokenNode = vars.get(getOriginalCodeText(singleExpressionContext)) != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                    tokenNode.setLineOfCode(singleExpressionContext.getStart().getLine());
                    tokenNode.setCode(getOriginalCodeText(singleExpressionContext));
                    AST.addVertex(tokenNode);
                    AST.addEdge(parentStack.peek(), tokenNode);
                } else {
                    // expression
                    visit(singleExpressionContext);
                }
            }
            parentStack.pop();
            return null;
        }

        @Override
        public String visitBitOrExpression(JavaScriptParser.BitOrExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            // operator
            ASNode opNode = new ASNode(ASNode.Type.OPERATOR);
            opNode.setLineOfCode(0);
            opNode.setCode("|");
            AST.addVertex(opNode);
            AST.addEdge(parentStack.peek(), opNode);
            parentStack.push(opNode);

            for (JavaScriptParser.SingleExpressionContext singleExpressionContext : ctx.singleExpression()) {
                if (singleExpressionContext.getChildCount() == 1) {
                    ASNode tokenNode;
                    tokenNode = vars.get(getOriginalCodeText(singleExpressionContext)) != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                    tokenNode.setLineOfCode(singleExpressionContext.getStart().getLine());
                    tokenNode.setCode(getOriginalCodeText(singleExpressionContext));
                    AST.addVertex(tokenNode);
                    AST.addEdge(parentStack.peek(), tokenNode);
                } else {
                    // expression
                    visit(singleExpressionContext);
                }
            }
            parentStack.pop();
            return null;
        }

        @Override
        public String visitAssignmentOperatorExpression(JavaScriptParser.AssignmentOperatorExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            // assignment operator
            ASNode assignNode = new ASNode(ASNode.Type.ASSIGNMENT);
            assignNode.setLineOfCode(ctx.assignmentOperator().getStart().getLine());
            assignNode.setCode(getOriginalCodeText(ctx.assignmentOperator()));
            AST.addVertex(assignNode);
            AST.addEdge(parentStack.peek(), assignNode);
            parentStack.push(assignNode);

            for (JavaScriptParser.SingleExpressionContext singleExpressionContext : ctx.singleExpression()) {
                if (singleExpressionContext.getChildCount() == 1) {
                    ASNode tokenNode;
                    tokenNode = vars.get(getOriginalCodeText(singleExpressionContext)) != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                    tokenNode.setLineOfCode(singleExpressionContext.getStart().getLine());
                    tokenNode.setCode(getOriginalCodeText(singleExpressionContext));
                    AST.addVertex(tokenNode);
                    AST.addEdge(parentStack.peek(), tokenNode);
                } else {
                    // expression
                    visit(singleExpressionContext);
                }
            }
            parentStack.pop();
            return null;
        }

        @Override
        public String visitVoidExpression(JavaScriptParser.VoidExpressionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            //    | Void singleExpression
            ASNode voidNode = new ASNode(ASNode.Type.VOID);
            voidNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(voidNode);
            AST.addEdge(parentStack.peek(), voidNode);
            ASNode block = new ASNode(ASNode.Type.BLOCK);
            block.setLineOfCode(ctx.singleExpression().getStart().getLine());
            AST.addVertex(block);
            AST.addEdge(voidNode, block);
            parentStack.push(block);
            visit(ctx.singleExpression());
            parentStack.pop();
            return "";
        }

        @Override
        public String visitCoalesceExpression(JavaScriptParser.CoalesceExpressionContext ctx) {
            //    | singleExpression '??' singleExpression                                # CoalesceExpression
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            // operator
            ASNode opNode = new ASNode(ASNode.Type.OPERATOR);
            opNode.setLineOfCode(0);
            opNode.setCode("??");
            AST.addVertex(opNode);
            AST.addEdge(parentStack.peek(), opNode);
            parentStack.push(opNode);

            for (JavaScriptParser.SingleExpressionContext singleExpressionContext : ctx.singleExpression()) {
                if (singleExpressionContext.getChildCount() == 1) {
                    ASNode tokenNode;
                    tokenNode = vars.get(getOriginalCodeText(singleExpressionContext)) != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                    tokenNode.setLineOfCode(singleExpressionContext.getStart().getLine());
                    tokenNode.setCode(getOriginalCodeText(singleExpressionContext));
                    AST.addVertex(tokenNode);
                    AST.addEdge(parentStack.peek(), tokenNode);
                } else {
                    // expression
                    visit(singleExpressionContext);
                }
            }
            parentStack.pop();
            return null;
        }

        @Override
        public String visitAssignable(JavaScriptParser.AssignableContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            if (ctx.identifier() != null) {
                return ctx.identifier().getText();
            } else if (ctx.arrayLiteral() != null) {
                return ctx.arrayLiteral().getText();
            }
            return ctx.objectLiteral().getText();
        }

        @Override
        public String visitObjectLiteral(JavaScriptParser.ObjectLiteralContext ctx) {
            // objectLiteral
            //    : '{' (propertyAssignment (',' propertyAssignment)* ','?)? '}'
            //    ;
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            ASNode objectNode = new ASNode(ASNode.Type.OBJECT_LITERAL);
            objectNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(objectNode);
            AST.addEdge(parentStack.peek(), objectNode);
            parentStack.push(objectNode);
            for (JavaScriptParser.PropertyAssignmentContext propertyAssignmentContext : ctx.propertyAssignment()) {
                visit(propertyAssignmentContext);
            }
            parentStack.pop();
            return null;
        }


//        @Override
//        public String visitFunctionDecl(JavaScriptParser.FunctionDeclContext ctx) {
//            if (ctx == null || ctx.isEmpty()) {
//                return "";
//            }
//            return visitFunctionDeclaration(ctx.functionDeclaration());
//        }

        @Override
        public String visitAnonymousFunctionDecl(JavaScriptParser.AnonymousFunctionDeclContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            StringBuffer stringBuffer = new StringBuffer();
            if (ctx.Async() != null) {
                stringBuffer.append(ctx.Async().getText() + " ");
            }
            stringBuffer.append(ctx.Function_() + "*(");
            stringBuffer.append(visitFormalParameterList(ctx.formalParameterList()) + ")");
            stringBuffer.append(visitFunctionBody(ctx.functionBody()));
            return stringBuffer.toString();
        }

        @Override
        public String visitArrowFunction(JavaScriptParser.ArrowFunctionContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            //TODO
            StringBuffer stringBuffer = new StringBuffer();
            if (ctx.Async() != null) {
                stringBuffer.append(ctx.Async().getText() + " ");
            }
            stringBuffer.append(visitArrowFunctionParameters(ctx.arrowFunctionParameters()));
            stringBuffer.append("=>");
            stringBuffer.append(visitArrowFunctionBody(ctx.arrowFunctionBody()));
            return stringBuffer.toString();
        }

        @Override
        public String visitArrowFunctionParameters(JavaScriptParser.ArrowFunctionParametersContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            if (ctx.identifier() != null) {
                return ctx.identifier().getText();
            }
            return visitFormalParameterList(ctx.formalParameterList());
        }

        @Override
        public String visitArrowFunctionBody(JavaScriptParser.ArrowFunctionBodyContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            if (ctx.singleExpression() != null) {
                return visit(ctx.singleExpression());
            }
            return visitFunctionBody(ctx.functionBody());
        }

//        @Override
//        public String visitAssignmentOperator(JavaScriptParser.AssignmentOperatorContext ctx) {
//            if (ctx == null || ctx.isEmpty()) {
//                return "";
//            }
//            return ctx.getText();
//        }

//        @Override
//        public String visitLiteral(JavaScriptParser.LiteralContext ctx) {
//            if (ctx == null || ctx.isEmpty()) {
//                return "";
//            }
//            if (ctx.NullLiteral() != null) {
//                return ctx.NullLiteral().getText();
//            } else if (ctx.BooleanLiteral() != null) {
//                return ctx.BooleanLiteral().getText();
//            } else if (ctx.RegularExpressionLiteral() != null) {
//                return ctx.RegularExpressionLiteral().getText();
//            } else if (ctx.templateStringLiteral() != null) {
//                return visitTemplateStringLiteral(ctx.templateStringLiteral());
//            } else if (ctx.numericLiteral() != null) {
//                return visitNumericLiteral(ctx.numericLiteral());
//            }
//            return visitBigintLiteral(ctx.bigintLiteral());
//        }

//        @Override
//        public String visitTemplateStringLiteral(JavaScriptParser.TemplateStringLiteralContext ctx) {
//            if (ctx == null || ctx.isEmpty()) {
//                return "";
//            }
//            StringBuffer stringBuffer = new StringBuffer();
//            stringBuffer.append(ctx.BackTick().get(0).getText() + " ");
//            for (JavaScriptParser.TemplateStringAtomContext atomContent : ctx.templateStringAtom()) {
//                stringBuffer.append(visitTemplateStringAtom(atomContent));
//                stringBuffer.append(" ");
//            }
//            for (int i = 1; i < ctx.BackTick().size(); i++) {
//                stringBuffer.append(ctx.BackTick().get(i).getText() + " ");
//            }
//            return stringBuffer.toString();
//        }
//
//        @Override
//        public String visitTemplateStringAtom(JavaScriptParser.TemplateStringAtomContext ctx) {
//            if (ctx == null || ctx.isEmpty()) {
//                return "";
//            }
//            if (ctx.TemplateStringAtom() != null) {
//                return ctx.TemplateStringAtom().getText();
//            }
//            return ctx.TemplateStringStartExpression().getText() + " " + visit(ctx.singleExpression()) + " " + ctx.TemplateCloseBrace().getText();
//        }
//
//        @Override
//        public String visitNumericLiteral(JavaScriptParser.NumericLiteralContext ctx) {
//            if (ctx == null || ctx.isEmpty()) {
//                return "";
//            }
//            if (ctx.DecimalLiteral() != null) {
//                return ctx.DecimalLiteral().getText();
//            } else if (ctx.HexIntegerLiteral() != null) {
//                return ctx.HexIntegerLiteral().getText();
//            } else if (ctx.OctalIntegerLiteral() != null) {
//                return ctx.OctalIntegerLiteral().getText();
//            } else if (ctx.OctalIntegerLiteral2() != null) {
//                return ctx.OctalIntegerLiteral2().getText();
//            }
//            return ctx.BinaryIntegerLiteral().getText();
//        }
//
//        @Override
//        public String visitBigintLiteral(JavaScriptParser.BigintLiteralContext ctx) {
//            if (ctx == null || ctx.isEmpty()) {
//                return "";
//            }
//            if (ctx.BigBinaryIntegerLiteral() != null) {
//                return ctx.BigBinaryIntegerLiteral().getText();
//            } else if (ctx.BigHexIntegerLiteral() != null) {
//                return ctx.BigHexIntegerLiteral().getText();
//            } else if (ctx.BigOctalIntegerLiteral() != null) {
//                return ctx.BigOctalIntegerLiteral().getText();
//            }
//            return ctx.BigBinaryIntegerLiteral().getText();
//        }

        @Override
        public String visitGetter(JavaScriptParser.GetterContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            if (ctx.identifier() == null || ctx.identifier().Identifier() == null) {
                visitStatement(ctx, null);
            } else {
                visitStatement(ctx, ctx.propertyName().getText());
            }
            return "";
        }

        @Override
        public String visitSetter(JavaScriptParser.SetterContext ctx) {
            if (ctx == null || ctx.isEmpty()) {
                return "";
            }
            if (ctx.identifier() == null || ctx.identifier().Identifier() == null) {
                visitStatement(ctx, null);
            } else {
                visitStatement(ctx, ctx.propertyName().getText());
            }
            return "";
        }

//        @Override
//        public String visitIdentifierName(JavaScriptParser.IdentifierNameContext ctx) {
//            if (ctx == null || ctx.isEmpty()) {
//                return "";
//            }
//            if (ctx.identifier() != null) {
//                return visitIdentifier(ctx.identifier());
//            }
//            return visitReservedWord(ctx.reservedWord());
//        }

//        @Override
//        public String visitIdentifier(JavaScriptParser.IdentifierContext ctx) {
//            if (ctx == null || ctx.isEmpty()) {
//                return "";
//            }
//            if (ctx.Identifier() != null) {
//                return ctx.Identifier().getText();
//            } else if (ctx.NonStrictLet() != null) {
//                return ctx.NonStrictLet().getText();
//            } else if (ctx.Async() != null) {
//                return ctx.Async().getText();
//            }
//            return ctx.As().getText();
//        }

//        @Override
//        public String visitReservedWord(JavaScriptParser.ReservedWordContext ctx) {
//            if (ctx == null || ctx.isEmpty()) {
//                return "";
//            }
//            if (ctx.keyword() != null) {
//                return visitKeyword(ctx.keyword());
//            } else if (ctx.NullLiteral() != null) {
//                return ctx.NullLiteral().getText();
//            } else if (ctx.BooleanLiteral() != null) {
//                return ctx.BooleanLiteral().getText();
//            }
//            return "";
//        }

//        @Override
//        public String visitKeyword(JavaScriptParser.KeywordContext ctx) {
//            if (ctx == null || ctx.isEmpty()) {
//                return "";
//            }
//            if (ctx.let_() != null && !ctx.let_().isEmpty()) {
//                return visitLet_(ctx.let_());
//            }
//            return getOriginalCodeText(ctx);
//        }

//        @Override
//        public String visitLet_(JavaScriptParser.Let_Context ctx) {
//            if (ctx == null || ctx.isEmpty()) {
//                return "";
//            }
//            if (ctx.NonStrictLet() == null) {
//                visitStatement(ctx, null);
//            } else {
//                visitStatement(ctx, "let $LABEL");
//            }
////            return ctx.StrictLet().getText();
//            return "";
//        }

//        @Override
//        public String visitEos(JavaScriptParser.EosContext ctx) {
//            if (ctx == null || ctx.isEmpty()) {
//                return "";
//            }
////            return "";
//            if (ctx.SemiColon() != null) {
//                visitStatement(ctx, "semiColon $LABEL");
//            } else if (ctx.EOF() != null) {
//                visitStatement(ctx, "eof $LABEL");
//            }
//            return "";
//        }

        private String getOriginalCodeText(ParserRuleContext ctx) {
            if (ctx == null)
                return "";
            int start = ctx.start.getStartIndex();
            int stop = ctx.stop.getStopIndex();
            Interval interval = new Interval(start, stop);
            return ctx.start.getInputStream().getText(interval);
        }

        private void visitStatement(ParserRuleContext ctx, String normalized) {
            if (ctx == null || ctx.isEmpty()) {
                return;
            }
            Logger.printf(Logger.Level.DEBUG, "Visiting: (%d)  %s", ctx.getStart().getLine(), getOriginalCodeText(ctx));
            ASNode statementNode = new ASNode(ASNode.Type.STATEMENT);
            statementNode.setCode(getOriginalCodeText(ctx));
            statementNode.setNormalizedCode(normalized);
            statementNode.setLineOfCode(ctx.getStart().getLine());
            Logger.debug("Adding statement " + ctx.getStart().getLine());
            AST.addVertex(statementNode);
            AST.addEdge(parentStack.peek(), statementNode);
        }
    }
}
